Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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),
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.6.1'
|
id 'com.diffplug.spotless' version '6.9.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -31,11 +31,11 @@ allprojects {
|
|||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
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.7.0'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||||
|
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||||
}
|
}
|
||||||
|
|||||||
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=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-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
|
||||||
|
|||||||
@@ -21,6 +21,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 +89,9 @@ 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-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 +133,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",
|
||||||
@@ -262,6 +276,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 +285,7 @@ public class JadxCLIArgs {
|
|||||||
args.setInsertDebugLines(addDebugLines);
|
args.setInsertDebugLines(addDebugLines);
|
||||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||||
args.setInlineMethods(inlineMethods);
|
args.setInlineMethods(inlineMethods);
|
||||||
|
args.setExtractFinally(extractFinally);
|
||||||
args.setRenameFlags(renameFlags);
|
args.setRenameFlags(renameFlags);
|
||||||
args.setFsCaseSensitive(fsCaseSensitive);
|
args.setFsCaseSensitive(fsCaseSensitive);
|
||||||
args.setCommentsLevel(commentsLevel);
|
args.setCommentsLevel(commentsLevel);
|
||||||
@@ -350,6 +366,10 @@ public class JadxCLIArgs {
|
|||||||
return inlineMethods;
|
return inlineMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isExtractFinally() {
|
||||||
|
return extractFinally;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
@@ -378,6 +398,10 @@ public class JadxCLIArgs {
|
|||||||
return deobfuscationParseKotlinMetadata;
|
return deobfuscationParseKotlinMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceNameSource getResourceNameSource() {
|
||||||
|
return resourceNameSource;
|
||||||
|
}
|
||||||
|
|
||||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||||
return useKotlinMethodsForVarNames;
|
return useKotlinMethodsForVarNames;
|
||||||
}
|
}
|
||||||
@@ -502,6 +526,19 @@ public class JadxCLIArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ResourceNameSourceConverter implements IStringConverter<ResourceNameSource> {
|
||||||
|
@Override
|
||||||
|
public ResourceNameSource convert(String value) {
|
||||||
|
try {
|
||||||
|
return ResourceNameSource.valueOf(value.toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'\'' + value + "' is unknown, possible values are: "
|
||||||
|
+ JadxCLIArgs.enumValuesString(ResourceNameSource.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
||||||
@Override
|
@Override
|
||||||
public DecompilationMode convert(String value) {
|
public DecompilationMode convert(String value) {
|
||||||
|
|||||||
@@ -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.9.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.2.2-7984345'
|
||||||
implementation 'com.google.protobuf:protobuf-java:3.11.4'
|
implementation 'com.google.protobuf:protobuf-java:3.21.5' // forcing latest version
|
||||||
}
|
|
||||||
|
|
||||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ 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.30.0'
|
||||||
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -72,6 +73,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;
|
||||||
@@ -369,6 +371,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 +550,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 +575,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
|
||||||
|
|||||||
@@ -37,8 +37,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;
|
||||||
@@ -358,8 +356,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);
|
||||||
}
|
}
|
||||||
@@ -496,25 +495,11 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
JavaMethod convertMethodNode(MethodNode method) {
|
JavaMethod convertMethodNode(MethodNode method) {
|
||||||
return methodsMap.computeIfAbsent(method, mthNode -> {
|
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||||
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
ClassNode parentCls = mthNode.getParentClass();
|
||||||
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
return new JavaMethod(convertClassNode(parentCls), mthNode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 codeCls;
|
|
||||||
}
|
|
||||||
return getCodeParentClass(codeCls);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||||
return getRoot().getClasses().stream()
|
return getRoot().getClasses().stream()
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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) {
|
||||||
@@ -369,6 +371,7 @@ public class ClassGen {
|
|||||||
mthGen.addInstructions(code);
|
mthGen.addInstructions(code);
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
|
code.attachAnnotation(NodeEnd.VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -761,6 +761,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());
|
||||||
@@ -823,9 +824,15 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public enum AFlag {
|
|||||||
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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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.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;
|
||||||
@@ -378,7 +379,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 +469,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 +660,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,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 +69,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 +81,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -61,6 +61,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;
|
||||||
@@ -316,6 +317,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ 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.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,23 +164,13 @@ 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);
|
||||||
@@ -189,6 +180,15 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
|
||||||
|
for (ResourceFile rf : resources) {
|
||||||
|
if (rf.getType() == ResourceType.ARSC) {
|
||||||
|
return rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void processResources(ResourceStorage resStorage) {
|
public void processResources(ResourceStorage resStorage) {
|
||||||
constValues.setResourcesNames(resStorage.getResourcesNames());
|
constValues.setResourcesNames(resStorage.getResourcesNames());
|
||||||
appPackage = resStorage.getAppPackage();
|
appPackage = resStorage.getAppPackage();
|
||||||
@@ -209,7 +209,7 @@ public class RootNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateObfuscatedFiles(ResTableParser parser, List<ResourceFile> resources) {
|
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
|
||||||
if (args.isSkipResources()) {
|
if (args.isSkipResources()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -59,6 +61,7 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
|||||||
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,6 +71,12 @@ 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
|
||||||
@@ -377,6 +386,7 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
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
|
||||||
@@ -417,13 +427,12 @@ public class EnumVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
||||||
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
|
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||||
FieldInfo valuesFieldInfo = valuesField.getFieldInfo();
|
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 +441,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 +488,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 +521,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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,10 @@ public class ProcessAnonymous extends AbstractVisitor {
|
|||||||
// exclude self usage
|
// exclude self usage
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (ctrUseCls.getTopParentClass().equals(cls)) {
|
||||||
|
// exclude usage inside inner classes
|
||||||
|
return null;
|
||||||
|
}
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
if (mth == ctr) {
|
if (mth == ctr) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -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,9 +1,6 @@
|
|||||||
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.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -31,7 +28,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 +46,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 +199,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 +209,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);
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import jadx.core.Consts;
|
|||||||
import jadx.core.codegen.json.JsonMappingGen;
|
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.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 +43,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,11 +194,6 @@ 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)) {
|
||||||
deobfuscator.forceRenameMethod(mth);
|
deobfuscator.forceRenameMethod(mth);
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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;
|
||||||
@@ -289,7 +290,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 +302,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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package jadx.core.xmlgen;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.api.ResourceType;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
public class ResDecoder {
|
||||||
|
|
||||||
|
public static IResParser decode(RootNode root, ResourceFile resFile, InputStream is) throws IOException {
|
||||||
|
if (resFile.getType() != ResourceType.ARSC) {
|
||||||
|
throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect ARSC");
|
||||||
|
}
|
||||||
|
IResParser parser = null;
|
||||||
|
String fileName = resFile.getOriginalName();
|
||||||
|
if (fileName.endsWith(".arsc")) {
|
||||||
|
parser = new ResTableParser(root);
|
||||||
|
}
|
||||||
|
if (fileName.endsWith(".pb")) {
|
||||||
|
parser = new ResProtoParser(root);
|
||||||
|
}
|
||||||
|
if (parser == null) {
|
||||||
|
throw new JadxRuntimeException("Unknown type of resource file: " + fileName);
|
||||||
|
}
|
||||||
|
parser.decode(is);
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,16 +20,16 @@ import com.android.aapt.Resources.Style;
|
|||||||
import com.android.aapt.Resources.Styleable;
|
import com.android.aapt.Resources.Styleable;
|
||||||
import com.android.aapt.Resources.Type;
|
import com.android.aapt.Resources.Type;
|
||||||
import com.android.aapt.Resources.Value;
|
import com.android.aapt.Resources.Value;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.core.xmlgen.entry.EntryConfig;
|
import jadx.core.xmlgen.entry.EntryConfig;
|
||||||
import jadx.core.xmlgen.entry.ProtoValue;
|
import jadx.core.xmlgen.entry.ProtoValue;
|
||||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
import jadx.core.xmlgen.entry.ValuesParser;
|
import jadx.core.xmlgen.entry.ValuesParser;
|
||||||
|
|
||||||
public class ResProtoParser {
|
public class ResProtoParser implements IResParser {
|
||||||
private final RootNode root;
|
private final RootNode root;
|
||||||
private final ResourceStorage resStorage = new ResourceStorage();
|
private final ResourceStorage resStorage = new ResourceStorage();
|
||||||
|
|
||||||
@@ -38,11 +38,7 @@ public class ResProtoParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
|
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
|
||||||
ResourceTable table = decodeProto(inputStream);
|
decode(inputStream);
|
||||||
for (Package p : table.getPackageList()) {
|
|
||||||
parse(p);
|
|
||||||
}
|
|
||||||
resStorage.finish();
|
|
||||||
ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames());
|
ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames());
|
||||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||||
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
|
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
|
||||||
@@ -50,6 +46,15 @@ public class ResProtoParser {
|
|||||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(InputStream inputStream) throws IOException {
|
||||||
|
ResourceTable table = ResourceTable.parseFrom(FileUtils.streamToByteArray(inputStream));
|
||||||
|
for (Package p : table.getPackageList()) {
|
||||||
|
parse(p);
|
||||||
|
}
|
||||||
|
resStorage.finish();
|
||||||
|
}
|
||||||
|
|
||||||
private void parse(Package p) {
|
private void parse(Package p) {
|
||||||
String name = p.getPackageName();
|
String name = p.getPackageName();
|
||||||
resStorage.setAppPackage(name);
|
resStorage.setAppPackage(name);
|
||||||
@@ -241,8 +246,13 @@ public class ResProtoParser {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceTable decodeProto(InputStream inputStream)
|
@Override
|
||||||
throws InvalidProtocolBufferException, IOException {
|
public ResourceStorage getResStorage() {
|
||||||
return ResourceTable.parseFrom(XmlGenUtils.readData(inputStream));
|
return resStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getStrings() {
|
||||||
|
return new String[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,25 @@ import java.util.Set;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.args.ResourceNameSource;
|
||||||
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.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
|
import jadx.core.utils.BetterName;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.xmlgen.entry.EntryConfig;
|
import jadx.core.xmlgen.entry.EntryConfig;
|
||||||
import jadx.core.xmlgen.entry.RawNamedValue;
|
import jadx.core.xmlgen.entry.RawNamedValue;
|
||||||
import jadx.core.xmlgen.entry.RawValue;
|
import jadx.core.xmlgen.entry.RawValue;
|
||||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||||
import jadx.core.xmlgen.entry.ValuesParser;
|
import jadx.core.xmlgen.entry.ValuesParser;
|
||||||
|
|
||||||
public class ResTableParser extends CommonBinaryParser {
|
public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);
|
||||||
|
|
||||||
private static final Pattern VALID_RES_KEY_PATTERN = Pattern.compile("[\\w\\d_]+");
|
private static final Pattern VALID_RES_KEY_PATTERN = Pattern.compile("[\\w\\d_]+");
|
||||||
@@ -76,6 +80,7 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
this.useRawResName = useRawResNames;
|
this.useRawResName = useRawResNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void decode(InputStream inputStream) throws IOException {
|
public void decode(InputStream inputStream) throws IOException {
|
||||||
is = new ParserStream(inputStream);
|
is = new ParserStream(inputStream);
|
||||||
decodeTableChunk();
|
decodeTableChunk();
|
||||||
@@ -93,14 +98,6 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceStorage getResStorage() {
|
|
||||||
return resStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getStrings() {
|
|
||||||
return strings;
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeTableChunk() throws IOException {
|
void decodeTableChunk() throws IOException {
|
||||||
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
|
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
|
||||||
is.checkInt16(0x000c, "Unexpected table header size");
|
is.checkInt16(0x000c, "Unexpected table header size");
|
||||||
@@ -294,35 +291,66 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
if (renamedKey != null) {
|
if (renamedKey != null) {
|
||||||
return renamedKey;
|
return renamedKey;
|
||||||
}
|
}
|
||||||
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
// styles might contain dots in name, search for alias only for resources names
|
||||||
if (constField != null) {
|
|
||||||
constField.add(AFlag.DONT_RENAME);
|
|
||||||
return constField.getName();
|
|
||||||
}
|
|
||||||
// styles might contain dots in name, use VALID_RES_KEY_PATTERN only for resource file name
|
|
||||||
if (typeName.equals("style")) {
|
if (typeName.equals("style")) {
|
||||||
return origKeyName;
|
return origKeyName;
|
||||||
} else if (VALID_RES_KEY_PATTERN.matcher(origKeyName).matches()) {
|
}
|
||||||
return origKeyName;
|
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
||||||
|
String resAlias = getResAlias(resRef, origKeyName, constField);
|
||||||
|
resStorage.addRename(resRef, resAlias);
|
||||||
|
if (constField != null) {
|
||||||
|
constField.rename(resAlias);
|
||||||
|
constField.add(AFlag.DONT_RENAME);
|
||||||
|
}
|
||||||
|
return resAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResAlias(int resRef, String origKeyName, @Nullable FieldNode constField) {
|
||||||
|
String name;
|
||||||
|
if (constField == null || constField.getTopParentClass().isSynthetic()) {
|
||||||
|
name = origKeyName;
|
||||||
|
} else {
|
||||||
|
name = getBetterName(root.getArgs().getResourceNameSource(), origKeyName, constField.getName());
|
||||||
|
}
|
||||||
|
Matcher matcher = VALID_RES_KEY_PATTERN.matcher(name);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
// Making sure origKeyName compliant with resource file name rules
|
// Making sure origKeyName compliant with resource file name rules
|
||||||
Matcher m = VALID_RES_KEY_PATTERN.matcher(origKeyName);
|
String cleanedResName = cleanName(matcher);
|
||||||
|
String newResName = String.format("res_0x%08x", resRef);
|
||||||
|
if (cleanedResName.isEmpty()) {
|
||||||
|
return newResName;
|
||||||
|
}
|
||||||
|
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
|
||||||
|
return newResName + "_" + cleanedResName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBetterName(ResourceNameSource nameSource, String resName, String codeName) {
|
||||||
|
switch (nameSource) {
|
||||||
|
case AUTO:
|
||||||
|
return BetterName.compareAndGet(resName, codeName);
|
||||||
|
case RESOURCES:
|
||||||
|
return resName;
|
||||||
|
case CODE:
|
||||||
|
return codeName;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unexpected ResourceNameSource value: " + nameSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanName(Matcher matcher) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
while (m.find()) {
|
while (matcher.find()) {
|
||||||
if (!first) {
|
if (!first) {
|
||||||
sb.append("_");
|
sb.append("_");
|
||||||
}
|
}
|
||||||
sb.append(m.group());
|
sb.append(matcher.group());
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
|
return sb.toString();
|
||||||
String newResName = String.format("res_0x%08x", resRef);
|
|
||||||
String cleanedResName = sb.toString();
|
|
||||||
if (!cleanedResName.isEmpty()) {
|
|
||||||
newResName += "_" + cleanedResName.toLowerCase();
|
|
||||||
}
|
|
||||||
return newResName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RawNamedValue parseValueMap() throws IOException {
|
private RawNamedValue parseValueMap() throws IOException {
|
||||||
@@ -434,4 +462,14 @@ public class ResTableParser extends CommonBinaryParser {
|
|||||||
is.skipToPos(start + length, "readScriptOrVariantChar");
|
is.skipToPos(start + length, "readScriptOrVariantChar");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceStorage getResStorage() {
|
||||||
|
return resStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getStrings() {
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,11 +168,13 @@ public class ResXmlGen {
|
|||||||
private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
||||||
String nameStr = vp.decodeNameRef(value.getNameRef());
|
String nameStr = vp.decodeNameRef(value.getNameRef());
|
||||||
String valueStr = vp.decodeValue(value.getRawValue());
|
String valueStr = vp.decodeValue(value.getRawValue());
|
||||||
|
int dataType = value.getRawValue().getDataType();
|
||||||
|
|
||||||
if (!typeName.equals("attr")) {
|
if (!typeName.equals("attr")) {
|
||||||
if (valueStr == null || valueStr.equals("0")) {
|
if (dataType == ParserConstants.TYPE_REFERENCE && (valueStr == null || valueStr.equals("0"))) {
|
||||||
valueStr = "@null";
|
valueStr = "@null";
|
||||||
}
|
}
|
||||||
if (nameStr != null) {
|
if (dataType == ParserConstants.TYPE_INT_DEC && nameStr != null) {
|
||||||
try {
|
try {
|
||||||
int intVal = Integer.parseInt(valueStr);
|
int intVal = Integer.parseInt(valueStr);
|
||||||
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
|
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package jadx.core.utils;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static jadx.core.utils.BetterName.calcRating;
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestBetterName {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
expectFirst("color_main", "t0");
|
||||||
|
expectFirst("done", "oOo0oO0o");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectFirst(String first, String second) {
|
||||||
|
String best = BetterName.compareAndGet(first, second);
|
||||||
|
assertThat(best)
|
||||||
|
.as(() -> String.format("'%s'=%d, '%s'=%d", first, calcRating(first), second, calcRating(second)))
|
||||||
|
.isEqualTo(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -555,7 +555,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
protected void enableDeobfuscation() {
|
protected void enableDeobfuscation() {
|
||||||
args.setDeobfuscationOn(true);
|
args.setDeobfuscationOn(true);
|
||||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||||
args.setDeobfuscationMinLength(2);
|
args.setDeobfuscationMinLength(2);
|
||||||
args.setDeobfuscationMaxLength(64);
|
args.setDeobfuscationMaxLength(64);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||||||
|
|
||||||
public abstract class SmaliTest extends IntegrationTest {
|
public abstract class SmaliTest extends IntegrationTest {
|
||||||
|
|
||||||
private static final String SMALI_TESTS_PROJECT = "jadx-core";
|
|
||||||
private static final String SMALI_TESTS_DIR = "src/test/smali";
|
private static final String SMALI_TESTS_DIR = "src/test/smali";
|
||||||
private static final String SMALI_TESTS_EXT = ".smali";
|
private static final String SMALI_TESTS_EXT = ".smali";
|
||||||
|
|
||||||
|
private String currentProject = "jadx-core";
|
||||||
|
|
||||||
|
public void setCurrentProject(String currentProject) {
|
||||||
|
this.currentProject = currentProject;
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void init() {
|
public void init() {
|
||||||
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip smali test for java input tests");
|
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip smali test for java input tests");
|
||||||
@@ -89,24 +94,24 @@ public abstract class SmaliTest extends IntegrationTest {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSmaliFile(String baseName) {
|
private File getSmaliFile(String baseName) {
|
||||||
File smaliFile = new File(SMALI_TESTS_DIR, baseName + SMALI_TESTS_EXT);
|
File smaliFile = new File(SMALI_TESTS_DIR, baseName + SMALI_TESTS_EXT);
|
||||||
if (smaliFile.exists()) {
|
if (smaliFile.exists()) {
|
||||||
return smaliFile;
|
return smaliFile;
|
||||||
}
|
}
|
||||||
File pathFromRoot = new File(SMALI_TESTS_PROJECT, smaliFile.getPath());
|
File pathFromRoot = new File(currentProject, smaliFile.getPath());
|
||||||
if (pathFromRoot.exists()) {
|
if (pathFromRoot.exists()) {
|
||||||
return pathFromRoot;
|
return pathFromRoot;
|
||||||
}
|
}
|
||||||
throw new AssertionError("Smali file not found: " + smaliFile.getPath());
|
throw new AssertionError("Smali file not found: " + smaliFile.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSmaliDir(String baseName) {
|
private File getSmaliDir(String baseName) {
|
||||||
File smaliDir = new File(SMALI_TESTS_DIR, baseName);
|
File smaliDir = new File(SMALI_TESTS_DIR, baseName);
|
||||||
if (smaliDir.exists()) {
|
if (smaliDir.exists()) {
|
||||||
return smaliDir;
|
return smaliDir;
|
||||||
}
|
}
|
||||||
File pathFromRoot = new File(SMALI_TESTS_PROJECT, smaliDir.getPath());
|
File pathFromRoot = new File(currentProject, smaliDir.getPath());
|
||||||
if (pathFromRoot.exists()) {
|
if (pathFromRoot.exists()) {
|
||||||
return pathFromRoot;
|
return pathFromRoot;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class TestCompiler implements Closeable {
|
|||||||
assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
||||||
return mth.invoke(inst, args);
|
return mth.invoke(inst, args);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
IntegrationTest.rethrow("Invoke error", e);
|
IntegrationTest.rethrow("Invoke error for method: " + methodName, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.assertj.core.api.Assertions;
|
|||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.ICodeNode;
|
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
@@ -58,11 +57,12 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkCodeAnnotationFor(String refStr, ICodeNode node) {
|
public JadxClassNodeAssertions checkCodeAnnotationFor(String refStr, ICodeAnnotation node) {
|
||||||
checkCodeAnnotationFor(refStr, 0, node);
|
checkCodeAnnotationFor(refStr, 0, node);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkCodeAnnotationFor(String refStr, int refOffset, ICodeNode node) {
|
public JadxClassNodeAssertions checkCodeAnnotationFor(String refStr, int refOffset, ICodeAnnotation node) {
|
||||||
ICodeInfo code = actual.getCode();
|
ICodeInfo code = actual.getCode();
|
||||||
int codePos = code.getCodeStr().indexOf(refStr);
|
int codePos = code.getCodeStr().indexOf(refStr);
|
||||||
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
||||||
@@ -70,9 +70,10 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
|||||||
for (Map.Entry<Integer, ICodeAnnotation> entry : code.getCodeMetadata().getAsMap().entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : code.getCodeMetadata().getAsMap().entrySet()) {
|
||||||
if (entry.getKey() == refPos) {
|
if (entry.getKey() == refPos) {
|
||||||
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
||||||
return;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fail("Annotation for reference string: '%s' at position %d not found", refStr, refPos);
|
fail("Annotation for reference string: '%s' at position %d not found", refStr, refPos);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package jadx.tests.integration.deobf;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestInheritedMethodRename extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
|
||||||
|
public static class A extends B {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class B {
|
||||||
|
public void call() {
|
||||||
|
System.out.println("call");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(A a) {
|
||||||
|
// reference to A.call() not renamed,
|
||||||
|
// should be resolved to B.call() and use alias
|
||||||
|
a.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
noDebugInfo();
|
||||||
|
enableDeobfuscation();
|
||||||
|
getArgs().setDeobfuscationMinLength(99);
|
||||||
|
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("public void m0call() {")
|
||||||
|
.doesNotContain(".call();")
|
||||||
|
.containsOne(".m0call();");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package jadx.tests.integration.enums;
|
package jadx.tests.integration.enums;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.tests.api.SmaliTest;
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
@@ -32,14 +34,31 @@ public class TestEnumObfuscated extends SmaliTest {
|
|||||||
public synthetic int getNum() {
|
public synthetic int getNum() {
|
||||||
return this.num;
|
return this.num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// custom values method
|
||||||
|
// should be kept and renamed to avoid collision to enum 'values()' method
|
||||||
|
public static int values() {
|
||||||
|
return new TestEnumObfuscated[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage of renamed 'values()' method, should be renamed back to 'values'
|
||||||
|
public static int valuesCount() {
|
||||||
|
return vs().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage of renamed '$VALUES' field, should be replaced with 'values()' method call
|
||||||
|
public static int valuesFieldUse() {
|
||||||
|
return $VLS.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
ClassNode cls = getClassNodeFromSmali();
|
getArgs().setCommentsLevel(CommentsLevel.WARN);
|
||||||
assertThat(cls)
|
getArgs().setRenameFlags(Collections.emptySet());
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
.code()
|
.code()
|
||||||
.doesNotContain("$VLS")
|
.doesNotContain("$VLS")
|
||||||
.doesNotContain("vo(")
|
.doesNotContain("vo(")
|
||||||
|
|||||||
+1
-3
@@ -56,8 +56,6 @@ public class TestGenericsMthOverride extends IntegrationTest {
|
|||||||
assertThat(code, containsOne("public Y method(Exception x) {"));
|
assertThat(code, containsOne("public Y method(Exception x) {"));
|
||||||
assertThat(code, containsOne("public Object method(Object x) {"));
|
assertThat(code, containsOne("public Object method(Object x) {"));
|
||||||
|
|
||||||
assertThat(code, countString(3, "@Override"));
|
assertThat(code, countString(4, "@Override"));
|
||||||
// TODO: @Override missing for class C
|
|
||||||
// assertThat(code, countString(4, "@Override"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package jadx.tests.integration.inline;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
@SuppressWarnings("CommentedOutCode")
|
||||||
|
public class TestOverlapSyntheticMethods extends SmaliTest {
|
||||||
|
// @formatter:off
|
||||||
|
/*
|
||||||
|
public String test(int i) {
|
||||||
|
return a(i) + "|" + a(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int a(int i) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String a(int i) {
|
||||||
|
return "i:" + i;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmali() {
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.containsOne("int a(int i) {")
|
||||||
|
.containsOne("String m0a(int i) {");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmaliNoRename() {
|
||||||
|
getArgs().setRenameFlags(Collections.emptySet());
|
||||||
|
disableCompilation();
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.containsOne("int a(int i) {")
|
||||||
|
.containsOne("String a(int i) {")
|
||||||
|
.containsOne("return a(i) + \"|\" + a(i);");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package jadx.tests.integration.inline;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestOverrideBridgeMerge extends SmaliTest {
|
||||||
|
|
||||||
|
public static class TestCls implements Function<String, Integer> {
|
||||||
|
@Override
|
||||||
|
public /* bridge */ /* synthetic */ Integer apply(String str) {
|
||||||
|
return test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer test(String str) {
|
||||||
|
return str.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.containsOne("Integer test(String str) {"); // not inlined
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmali() {
|
||||||
|
ClassNode cls = getClassNodeFromSmali();
|
||||||
|
ICodeAnnotation mthDef = new NodeDeclareRef(getMethod(cls, "apply"));
|
||||||
|
assertThat(cls)
|
||||||
|
.checkCodeAnnotationFor("apply(String str) {", mthDef)
|
||||||
|
.code()
|
||||||
|
.containsOne("@Override")
|
||||||
|
.containsOne("public Integer apply(String str) {")
|
||||||
|
.doesNotContain("test(String str)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package jadx.tests.integration.inner;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.JadxInternalAccess;
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.core.dex.attributes.AType;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestAnonymousClass20 extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unused", "checkstyle:TypeName", "Convert2Lambda", "Anonymous2MethodRef" })
|
||||||
|
public static class Test$Cls {
|
||||||
|
public Runnable test() {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
new Test$Cls();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(Test$Cls.class);
|
||||||
|
assertThat(cls.get(AType.ANONYMOUS_CLASS)).isNull();
|
||||||
|
|
||||||
|
JavaClass javaClass = JadxInternalAccess.convertClassNode(jadxDecompiler, cls);
|
||||||
|
assertThat(javaClass.getTopParentClass()).isEqualTo(javaClass);
|
||||||
|
|
||||||
|
assertThat(cls)
|
||||||
|
.code()
|
||||||
|
.containsOne("new TestAnonymousClass20$Test$Cls();");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package jadx.tests.integration.loops;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty endless loop, issue #1611
|
||||||
|
*/
|
||||||
|
public class TestEndlessLoop2 extends SmaliTest {
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
disableCompilation();
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.countString(2, "while (true) {");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package jadx.tests.integration.names;
|
package jadx.tests.integration.names;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.tests.integration.others;
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ import jadx.api.JadxInternalAccess;
|
|||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaMethod;
|
import jadx.api.JavaMethod;
|
||||||
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.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
@@ -53,14 +55,25 @@ public class TestCodeMetadata extends IntegrationTest {
|
|||||||
int callUse = callUsePlaces.get(0);
|
int callUse = callUsePlaces.get(0);
|
||||||
|
|
||||||
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
||||||
|
System.out.println(metadata);
|
||||||
ICodeNodeRef callDef = metadata.getNodeAt(callUse);
|
ICodeNodeRef callDef = metadata.getNodeAt(callUse);
|
||||||
assertThat(callDef).isSameAs(testMth);
|
assertThat(callDef).isSameAs(testMth);
|
||||||
|
|
||||||
int beforeCallDef = callDefPos - 10;
|
AtomicInteger endPos = new AtomicInteger();
|
||||||
ICodeAnnotation closest = metadata.getClosestUp(beforeCallDef);
|
ICodeAnnotation testEnd = metadata.searchUp(callDefPos, (pos, ann) -> {
|
||||||
|
if (ann.getAnnType() == AnnType.END) {
|
||||||
|
endPos.set(pos);
|
||||||
|
return ann;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
assertThat(testEnd).isNotNull();
|
||||||
|
int testEndPos = endPos.get();
|
||||||
|
|
||||||
|
ICodeAnnotation closest = metadata.getClosestUp(testEndPos);
|
||||||
assertThat(closest).isInstanceOf(FieldNode.class); // field reference from 'return a.str;'
|
assertThat(closest).isInstanceOf(FieldNode.class); // field reference from 'return a.str;'
|
||||||
|
|
||||||
ICodeNodeRef nodeBelow = metadata.getNodeBelow(beforeCallDef);
|
ICodeNodeRef nodeBelow = metadata.getNodeBelow(testEndPos);
|
||||||
assertThat(nodeBelow).isSameAs(callMth);
|
assertThat(nodeBelow).isSameAs(callMth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.JavaMethod;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.api.JadxInternalAccess.convertClassNode;
|
||||||
|
import static jadx.api.JadxInternalAccess.convertMethodNode;
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestCodeMetadata2 extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
@SuppressWarnings("Convert2Lambda")
|
||||||
|
public Runnable test(boolean a) {
|
||||||
|
if (a) {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
System.out.println("another");
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Runnable empty() {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
assertThat(cls).code().containsOne("return empty();");
|
||||||
|
|
||||||
|
MethodNode testMth = getMethod(cls, "test");
|
||||||
|
MethodNode emptyMth = getMethod(cls, "empty");
|
||||||
|
|
||||||
|
JavaClass javaClass = convertClassNode(jadxDecompiler, cls);
|
||||||
|
JavaMethod emptyJavaMethod = convertMethodNode(jadxDecompiler, emptyMth);
|
||||||
|
List<Integer> emptyUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), emptyJavaMethod);
|
||||||
|
assertThat(emptyUsePlaces).hasSize(1);
|
||||||
|
int callUse = emptyUsePlaces.get(0);
|
||||||
|
|
||||||
|
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
||||||
|
assertThat(metadata.getNodeAt(callUse)).isSameAs(testMth);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negative case for field initialization move (#1599).
|
||||||
|
* Can't reorder with other instance methods.
|
||||||
|
*/
|
||||||
|
public class TestFieldInitNegative extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
StringBuilder sb;
|
||||||
|
int field;
|
||||||
|
|
||||||
|
public TestCls() {
|
||||||
|
initBuilder(new StringBuilder("sb"));
|
||||||
|
this.field = initField();
|
||||||
|
this.sb.append(this.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initBuilder(StringBuilder sb) {
|
||||||
|
this.sb = sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int initField() {
|
||||||
|
return sb.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStr() {
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
assertThat(new TestCls().getStr()).isEqualTo("sb2"); // no NPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.doesNotContain("int field = initField();")
|
||||||
|
.containsOne("this.field = initField();");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ public class TestMoveInline extends SmaliTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
getArgs().setRawCFGOutput(true);
|
|
||||||
assertThat(getClassNodeFromSmali())
|
assertThat(getClassNodeFromSmali())
|
||||||
.code()
|
.code()
|
||||||
// check operations order
|
// check operations order
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package jadx.tests.integration.trycatch;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestNestedTryCatch4 extends SmaliTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
disableCompilation();
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.doesNotContain("?? ");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package jadx.tests.integration.trycatch;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negative test case for finally extract (issue 1592).
|
||||||
|
* Different registers incorrectly merged into one.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "CommentedOutCode", "GrazieInspection" })
|
||||||
|
public class TestTryCatchFinally15 extends SmaliTest {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
/*
|
||||||
|
protected final Parcel test(int i, Parcel parcel) throws RemoteException {
|
||||||
|
Parcel obtain = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
this.zza.transact(i, parcel, obtain, 0);
|
||||||
|
obtain.readException();
|
||||||
|
return obtain;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
obtain.recycle();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
disableCompilation();
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.doesNotContain("parcel = Parcel.obtain();")
|
||||||
|
.containsOne("this.zza.transact(i, parcel, obtain, 0);");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
||||||
|
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
public class TestTryCatchFinally6 extends IntegrationTest {
|
public class TestTryCatchFinally6 extends IntegrationTest {
|
||||||
@@ -54,15 +55,7 @@ public class TestTryCatchFinally6 extends IntegrationTest {
|
|||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String code = cls.getCode().toString();
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
assertThat(code, containsLines(2,
|
// impossible to proof that variables should be merged, so can't restore finally block here
|
||||||
"FileInputStream fileInputStream = null;",
|
assertThat(code, containsOne("if (0 != 0) {"));
|
||||||
"try {",
|
|
||||||
indent() + "call();",
|
|
||||||
indent() + "fileInputStream = new FileInputStream(\"1.txt\");",
|
|
||||||
"} finally {",
|
|
||||||
indent() + "if (fileInputStream != null) {",
|
|
||||||
indent() + indent() + "fileInputStream.close();",
|
|
||||||
indent() + '}',
|
|
||||||
"}"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,27 @@
|
|||||||
return-object v0
|
return-object v0
|
||||||
.end method
|
.end method
|
||||||
|
|
||||||
|
.method public static values()[Lenums/TestEnumObfuscated;
|
||||||
|
.registers 1
|
||||||
|
sget-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated;
|
||||||
|
return v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static valuesCount()I
|
||||||
|
.registers 2
|
||||||
|
invoke-static {v0, p0}, Lenums/TestEnumObfuscated;->vs()[Lenums/TestEnumObfuscated;
|
||||||
|
move-result-object v0
|
||||||
|
array-length v1, v0
|
||||||
|
return v1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static valuesFieldUse()I
|
||||||
|
.registers 2
|
||||||
|
sget-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated;
|
||||||
|
array-length v1, v0
|
||||||
|
return v1
|
||||||
|
.end method
|
||||||
|
|
||||||
.method public synthetic getNum()I
|
.method public synthetic getNum()I
|
||||||
.registers 2
|
.registers 2
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
.class public Linline/TestOverlapSyntheticMethods;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.method public synthetic a(I)I
|
||||||
|
.registers 2
|
||||||
|
return p1
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public synthetic a(I)Ljava/lang/String;
|
||||||
|
.registers 4
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
const-string v1, "i:"
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public test(I)Ljava/lang/String;
|
||||||
|
.registers 4
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
invoke-virtual {p0, p1}, Linline/TestOverlapSyntheticMethods;->a(I)I
|
||||||
|
move-result v1
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
move-result-object v0
|
||||||
|
const-string v1, "|"
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {p0, p1}, Linline/TestOverlapSyntheticMethods;->a(I)Ljava/lang/String;
|
||||||
|
move-result-object v1
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.class public Linline/TestOverrideBridgeMerge;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.implements Ljava/util/function/Function;
|
||||||
|
|
||||||
|
.annotation system Ldalvik/annotation/Signature;
|
||||||
|
value = {
|
||||||
|
"Ljava/lang/Object;",
|
||||||
|
"Ljava/util/function/Function",
|
||||||
|
"<",
|
||||||
|
"Ljava/lang/String;",
|
||||||
|
"Ljava/lang/Integer;",
|
||||||
|
">;"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.method public constructor <init>()V
|
||||||
|
.registers 1
|
||||||
|
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public bridge synthetic apply(Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
.registers 3
|
||||||
|
check-cast p1, Ljava/lang/String;
|
||||||
|
invoke-virtual {p0, p1}, Linline/TestOverrideBridgeMerge;->test(Ljava/lang/String;)Ljava/lang/Integer;
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public test(Ljava/lang/String;)Ljava/lang/Integer;
|
||||||
|
.registers 3
|
||||||
|
.param p1, "str" # Ljava/lang/String;
|
||||||
|
invoke-virtual {p1}, Ljava/lang/String;->length()I
|
||||||
|
move-result v0
|
||||||
|
invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
.class Lloops/TestEndlessLoop2;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.field instanceCount:J
|
||||||
|
|
||||||
|
.method test([Ljava/lang/String;)V
|
||||||
|
.registers 10
|
||||||
|
|
||||||
|
const/16 p1, 0xb
|
||||||
|
invoke-virtual {p0, p1}, Lloops/TestEndlessLoop2;->vMeth(I)V
|
||||||
|
const/16 v0, 0xf1
|
||||||
|
const-wide/high16 v1, 0x4032000000000000L # 18.0
|
||||||
|
|
||||||
|
:goto_a
|
||||||
|
const-wide/high16 v3, 0x4076000000000000L # 352.0
|
||||||
|
const/4 v5, 0x1
|
||||||
|
cmpg-double v6, v1, v3
|
||||||
|
if-gez v6, :cond_1c
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
:goto_12
|
||||||
|
add-int/2addr v0, v5
|
||||||
|
const/16 v3, 0x4b
|
||||||
|
if-ge v0, v3, :cond_18
|
||||||
|
goto :goto_12
|
||||||
|
|
||||||
|
:cond_18
|
||||||
|
const-wide/high16 v3, 0x3ff0000000000000L # 1.0
|
||||||
|
add-double/2addr v1, v3
|
||||||
|
goto :goto_a
|
||||||
|
|
||||||
|
:cond_1c
|
||||||
|
iget-wide v3, p0, Lloops/TestEndlessLoop2;->instanceCount:J
|
||||||
|
long-to-int v4, v3
|
||||||
|
const/16 v3, 0xb
|
||||||
|
|
||||||
|
:goto_21
|
||||||
|
const/16 v6, 0xf3
|
||||||
|
|
||||||
|
if-ge v5, v6, :cond_41
|
||||||
|
rem-int/lit8 v6, v5, 0x9
|
||||||
|
add-int/lit8 v6, v6, 0x12
|
||||||
|
if-eq v6, p1, :cond_3e
|
||||||
|
const/16 v7, 0x15
|
||||||
|
if-eq v6, v7, :cond_36
|
||||||
|
const/16 v7, 0x16
|
||||||
|
if-eq v6, v7, :cond_34
|
||||||
|
goto :goto_3b
|
||||||
|
|
||||||
|
|
||||||
|
:cond_34
|
||||||
|
add-int/2addr v4, v0
|
||||||
|
goto :goto_3b
|
||||||
|
|
||||||
|
:cond_36
|
||||||
|
const v6, 0xeed9
|
||||||
|
div-int/2addr v3, v6
|
||||||
|
nop
|
||||||
|
|
||||||
|
:goto_3b
|
||||||
|
add-int/lit8 v5, v5, 0x1
|
||||||
|
goto :goto_21
|
||||||
|
|
||||||
|
:cond_3e
|
||||||
|
nop
|
||||||
|
|
||||||
|
:goto_3f
|
||||||
|
nop
|
||||||
|
# endless loop with empty body
|
||||||
|
goto :goto_3f
|
||||||
|
|
||||||
|
:cond_41
|
||||||
|
sget-object p1, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||||
|
invoke-static {v1, v2}, Ljava/lang/Double;->doubleToLongBits(D)J
|
||||||
|
move-result-wide v0
|
||||||
|
new-instance v2, Ljava/lang/StringBuilder;
|
||||||
|
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
const-string v5, "i21 d2 i22 = "
|
||||||
|
invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
const-string v3, ","
|
||||||
|
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
invoke-virtual {v2, v0, v1}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;
|
||||||
|
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
invoke-virtual {v2, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||||
|
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
move-result-object v0
|
||||||
|
invoke-virtual {p1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,601 @@
|
|||||||
|
.class public Ltrycatch/TestNestedTryCatch4;
|
||||||
|
.super Landroid/app/NativeActivity;
|
||||||
|
|
||||||
|
.method private test(Landroid/content/Intent;)V
|
||||||
|
.registers 11
|
||||||
|
.annotation system Ldalvik/annotation/MethodParameters;
|
||||||
|
accessFlags = {
|
||||||
|
0x0
|
||||||
|
}
|
||||||
|
names = {
|
||||||
|
"intent"
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
const-string v0, "IOException while closing input stream\n"
|
||||||
|
|
||||||
|
if-nez p1, :cond_5
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:cond_5
|
||||||
|
const-string v1, "intent_cmd"
|
||||||
|
|
||||||
|
.line 1740
|
||||||
|
invoke-virtual {p1, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
const-string v2, "MCPE"
|
||||||
|
|
||||||
|
if-eqz v1, :cond_80
|
||||||
|
|
||||||
|
.line 1741
|
||||||
|
invoke-virtual {v1}, Ljava/lang/String;->length()I
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-lez v3, :cond_80
|
||||||
|
|
||||||
|
.line 1743
|
||||||
|
:try_start_15
|
||||||
|
new-instance p1, Lorg/json/JSONObject;
|
||||||
|
|
||||||
|
invoke-direct {p1, v1}, Lorg/json/JSONObject;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
const-string v0, "Command"
|
||||||
|
|
||||||
|
.line 1744
|
||||||
|
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
const-string v1, "keyboardResult"
|
||||||
|
|
||||||
|
.line 1745
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-eqz v1, :cond_33
|
||||||
|
|
||||||
|
const-string v0, "Text"
|
||||||
|
|
||||||
|
.line 1746
|
||||||
|
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {p0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeSetTextboxText(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
goto/16 :goto_208
|
||||||
|
|
||||||
|
:cond_33
|
||||||
|
const-string v1, "fileDialogResult"
|
||||||
|
|
||||||
|
.line 1748
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :cond_208
|
||||||
|
|
||||||
|
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||||
|
|
||||||
|
const-wide/16 v3, 0x0
|
||||||
|
|
||||||
|
cmp-long v5, v0, v3
|
||||||
|
|
||||||
|
if-eqz v5, :cond_208
|
||||||
|
|
||||||
|
const-string v0, "Result"
|
||||||
|
|
||||||
|
.line 1749
|
||||||
|
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
const-string v1, "Ok"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
if-eqz v0, :cond_5d
|
||||||
|
|
||||||
|
.line 1750
|
||||||
|
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||||
|
|
||||||
|
const-string v5, "Path"
|
||||||
|
|
||||||
|
invoke-virtual {p1, v5}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0, v1, p1}, Ltrycatch/TestNestedTryCatch4;->nativeOnPickImageSuccess(JLjava/lang/String;)V
|
||||||
|
|
||||||
|
goto :goto_62
|
||||||
|
|
||||||
|
.line 1753
|
||||||
|
:cond_5d
|
||||||
|
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0, v1}, Ltrycatch/TestNestedTryCatch4;->nativeOnPickImageCanceled(J)V
|
||||||
|
|
||||||
|
.line 1755
|
||||||
|
:goto_62
|
||||||
|
iput-wide v3, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||||
|
:try_end_64
|
||||||
|
.catch Lorg/json/JSONException; {:try_start_15 .. :try_end_64} :catch_66
|
||||||
|
|
||||||
|
goto/16 :goto_208
|
||||||
|
|
||||||
|
:catch_66
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 1759
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
const-string v1, "JSONObject exception:"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Lorg/json/JSONException;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-static {v2, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 1765
|
||||||
|
:cond_80
|
||||||
|
invoke-virtual {p1}, Landroid/content/Intent;->getAction()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
.line 1766
|
||||||
|
invoke-virtual {p1}, Landroid/content/Intent;->getType()Ljava/lang/String;
|
||||||
|
|
||||||
|
const-string/jumbo v3, "xbox_live_game_invite"
|
||||||
|
|
||||||
|
.line 1768
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-eqz v3, :cond_b0
|
||||||
|
|
||||||
|
const-string/jumbo v0, "xbl"
|
||||||
|
|
||||||
|
.line 1771
|
||||||
|
invoke-virtual {p1, v0}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
.line 1772
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
const-string v3, "[XboxLive] Received Invite "
|
||||||
|
|
||||||
|
invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-static {v2, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
.line 1776
|
||||||
|
invoke-virtual {p0, v1, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
goto/16 :goto_208
|
||||||
|
|
||||||
|
:cond_b0
|
||||||
|
const-string v3, "android.intent.action.VIEW"
|
||||||
|
|
||||||
|
.line 1779
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-nez v3, :cond_c0
|
||||||
|
|
||||||
|
const-string v3, "org.chromium.arc.intent.action.VIEW"
|
||||||
|
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-eqz v1, :cond_208
|
||||||
|
|
||||||
|
.line 1780
|
||||||
|
:cond_c0
|
||||||
|
invoke-virtual {p1}, Landroid/content/Intent;->getScheme()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
.line 1781
|
||||||
|
invoke-virtual {p1}, Landroid/content/Intent;->getData()Landroid/net/Uri;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
if-nez p1, :cond_cb
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:cond_cb
|
||||||
|
const-string v3, "minecraft"
|
||||||
|
|
||||||
|
.line 1787
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-nez v3, :cond_1f9
|
||||||
|
|
||||||
|
const-string v3, "minecraftedu"
|
||||||
|
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
if-eqz v3, :cond_dd
|
||||||
|
|
||||||
|
goto/16 :goto_1f9
|
||||||
|
|
||||||
|
:cond_dd
|
||||||
|
const-string v3, "file"
|
||||||
|
|
||||||
|
.line 1795
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
const-string v4, "&"
|
||||||
|
|
||||||
|
if-eqz v3, :cond_108
|
||||||
|
|
||||||
|
.line 1798
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
const-string v0, "fileIntent"
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
goto/16 :goto_208
|
||||||
|
|
||||||
|
:cond_108
|
||||||
|
const-string v3, "content"
|
||||||
|
|
||||||
|
.line 1800
|
||||||
|
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-eqz v1, :cond_208
|
||||||
|
|
||||||
|
.line 1803
|
||||||
|
new-instance v1, Ljava/io/File;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v3
|
||||||
|
|
||||||
|
invoke-direct {v1, v3}, Ljava/io/File;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/io/File;->getName()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
.line 1804
|
||||||
|
new-instance v3, Ljava/io/File;
|
||||||
|
|
||||||
|
new-instance v5, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {p0}, Ltrycatch/TestNestedTryCatch4;->getApplicationContext()Landroid/content/Context;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
invoke-virtual {v6}, Landroid/content/Context;->getCacheDir()Ljava/io/File;
|
||||||
|
|
||||||
|
move-result-object v6
|
||||||
|
|
||||||
|
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
const-string v6, "/"
|
||||||
|
|
||||||
|
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v5, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
invoke-direct {v3, v1}, Ljava/io/File;-><init>(Ljava/lang/String;)V
|
||||||
|
|
||||||
|
.line 1806
|
||||||
|
invoke-virtual {p0}, Ltrycatch/TestNestedTryCatch4;->getContentResolver()Landroid/content/ContentResolver;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
.line 1810
|
||||||
|
:try_start_142
|
||||||
|
invoke-virtual {v1, p1}, Landroid/content/ContentResolver;->openInputStream(Landroid/net/Uri;)Ljava/io/InputStream;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
:try_end_146
|
||||||
|
.catch Ljava/io/IOException; {:try_start_142 .. :try_end_146} :catch_1df
|
||||||
|
|
||||||
|
.line 1818
|
||||||
|
:try_start_146
|
||||||
|
new-instance v5, Ljava/io/FileOutputStream;
|
||||||
|
|
||||||
|
invoke-direct {v5, v3}, Ljava/io/FileOutputStream;-><init>(Ljava/io/File;)V
|
||||||
|
|
||||||
|
const/high16 v6, 0x100000
|
||||||
|
|
||||||
|
new-array v6, v6, [B
|
||||||
|
|
||||||
|
.line 1824
|
||||||
|
:goto_14f
|
||||||
|
invoke-virtual {v1, v6}, Ljava/io/InputStream;->read([B)I
|
||||||
|
|
||||||
|
move-result v7
|
||||||
|
|
||||||
|
const/4 v8, -0x1
|
||||||
|
|
||||||
|
if-eq v7, v8, :cond_15b
|
||||||
|
|
||||||
|
const/4 v8, 0x0
|
||||||
|
|
||||||
|
.line 1825
|
||||||
|
invoke-virtual {v5, v6, v8, v7}, Ljava/io/OutputStream;->write([BII)V
|
||||||
|
|
||||||
|
goto :goto_14f
|
||||||
|
|
||||||
|
.line 1828
|
||||||
|
:cond_15b
|
||||||
|
invoke-virtual {v5}, Ljava/io/OutputStream;->close()V
|
||||||
|
|
||||||
|
const-string v5, "contentIntent"
|
||||||
|
|
||||||
|
.line 1831
|
||||||
|
new-instance v6, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v6}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v6, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v6, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v6, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v6}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {p0, v5, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
:try_end_17d
|
||||||
|
.catch Ljava/io/IOException; {:try_start_146 .. :try_end_17d} :catch_18b
|
||||||
|
.catchall {:try_start_146 .. :try_end_17d} :catchall_189
|
||||||
|
|
||||||
|
.line 1842
|
||||||
|
:try_start_17d
|
||||||
|
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||||
|
:try_end_180
|
||||||
|
.catch Ljava/io/IOException; {:try_start_17d .. :try_end_180} :catch_182
|
||||||
|
|
||||||
|
goto/16 :goto_208
|
||||||
|
|
||||||
|
:catch_182
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 1845
|
||||||
|
new-instance v1, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
goto :goto_1b1
|
||||||
|
|
||||||
|
:catchall_189
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
goto :goto_1c3
|
||||||
|
|
||||||
|
:catch_18b
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 1833
|
||||||
|
:try_start_18c
|
||||||
|
new-instance v4, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
const-string v5, "IOException while copying file from content intent\n"
|
||||||
|
|
||||||
|
invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v4, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
:try_end_1a4
|
||||||
|
.catchall {:try_start_18c .. :try_end_1a4} :catchall_189
|
||||||
|
|
||||||
|
.line 1837
|
||||||
|
:try_start_1a4
|
||||||
|
invoke-virtual {v3}, Ljava/io/File;->delete()Z
|
||||||
|
:try_end_1a7
|
||||||
|
.catch Ljava/lang/Exception; {:try_start_1a4 .. :try_end_1a7} :catch_1a7
|
||||||
|
.catchall {:try_start_1a4 .. :try_end_1a7} :catchall_189
|
||||||
|
|
||||||
|
.line 1842
|
||||||
|
:catch_1a7
|
||||||
|
:try_start_1a7
|
||||||
|
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||||
|
:try_end_1aa
|
||||||
|
.catch Ljava/io/IOException; {:try_start_1a7 .. :try_end_1aa} :catch_1ab
|
||||||
|
|
||||||
|
goto :goto_208
|
||||||
|
|
||||||
|
:catch_1ab
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 1845
|
||||||
|
new-instance v1, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
:goto_1b1
|
||||||
|
invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
goto :goto_208
|
||||||
|
|
||||||
|
.line 1842
|
||||||
|
:goto_1c3
|
||||||
|
:try_start_1c3
|
||||||
|
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||||
|
:try_end_1c6
|
||||||
|
.catch Ljava/io/IOException; {:try_start_1c3 .. :try_end_1c6} :catch_1c7
|
||||||
|
|
||||||
|
goto :goto_1de
|
||||||
|
|
||||||
|
:catch_1c7
|
||||||
|
move-exception v1
|
||||||
|
|
||||||
|
.line 1845
|
||||||
|
new-instance v3, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-static {v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
.line 1847
|
||||||
|
:goto_1de
|
||||||
|
throw p1
|
||||||
|
|
||||||
|
:catch_1df
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 1813
|
||||||
|
new-instance v0, Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||||
|
|
||||||
|
const-string v1, "IOException while opening file from content intent\n"
|
||||||
|
|
||||||
|
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 1788
|
||||||
|
:cond_1f9
|
||||||
|
:goto_1f9
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getHost()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
.line 1789
|
||||||
|
invoke-virtual {p1}, Landroid/net/Uri;->getQuery()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object p1
|
||||||
|
|
||||||
|
if-nez v0, :cond_205
|
||||||
|
|
||||||
|
if-eqz p1, :cond_208
|
||||||
|
|
||||||
|
.line 1792
|
||||||
|
:cond_205
|
||||||
|
invoke-virtual {p0, v0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|
||||||
|
:cond_208
|
||||||
|
:goto_208
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.class public Ltrycatch/TestTryCatchFinally15;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.implements Landroid/os/IInterface;
|
||||||
|
|
||||||
|
.field private final zza:Landroid/os/IBinder;
|
||||||
|
.field private final zzb:Ljava/lang/String;
|
||||||
|
|
||||||
|
.method protected final test(ILandroid/os/Parcel;)Landroid/os/Parcel;
|
||||||
|
.registers 6
|
||||||
|
.annotation system Ldalvik/annotation/Throws;
|
||||||
|
value = {
|
||||||
|
Landroid/os/RemoteException;
|
||||||
|
}
|
||||||
|
.end annotation
|
||||||
|
|
||||||
|
.line 1
|
||||||
|
invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
:try_start_4
|
||||||
|
iget-object v1, p0, Ltrycatch/TestTryCatchFinally15;->zza:Landroid/os/IBinder;
|
||||||
|
const/4 v2, 0x0
|
||||||
|
|
||||||
|
.line 2
|
||||||
|
invoke-interface {v1, p1, p2, v0, v2}, Landroid/os/IBinder;->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
|
||||||
|
.line 3
|
||||||
|
invoke-virtual {v0}, Landroid/os/Parcel;->readException()V
|
||||||
|
:try_end_d
|
||||||
|
.catch Ljava/lang/RuntimeException; {:try_start_4 .. :try_end_d} :catch_13
|
||||||
|
.catchall {:try_start_4 .. :try_end_d} :catchall_11
|
||||||
|
|
||||||
|
.line 6
|
||||||
|
invoke-virtual {p2}, Landroid/os/Parcel;->recycle()V
|
||||||
|
return-object v0
|
||||||
|
|
||||||
|
:catchall_11
|
||||||
|
move-exception p1
|
||||||
|
goto :goto_18
|
||||||
|
|
||||||
|
.line 5
|
||||||
|
:catch_13
|
||||||
|
move-exception p1
|
||||||
|
|
||||||
|
.line 4
|
||||||
|
:try_start_14
|
||||||
|
invoke-virtual {v0}, Landroid/os/Parcel;->recycle()V
|
||||||
|
|
||||||
|
.line 5
|
||||||
|
throw p1
|
||||||
|
:try_end_18
|
||||||
|
.catchall {:try_start_14 .. :try_end_18} :catchall_11
|
||||||
|
|
||||||
|
.line 6
|
||||||
|
:goto_18
|
||||||
|
invoke-virtual {p2}, Landroid/os/Parcel;->recycle()V
|
||||||
|
|
||||||
|
.line 7
|
||||||
|
throw p1
|
||||||
|
.end method
|
||||||
+12
-6
@@ -15,18 +15,18 @@ dependencies {
|
|||||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||||
|
|
||||||
implementation 'com.formdev:flatlaf:2.3'
|
implementation 'com.formdev:flatlaf:2.4'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:2.3'
|
implementation 'com.formdev:flatlaf-intellij-themes:2.4'
|
||||||
implementation 'com.formdev:flatlaf-extras:2.3'
|
implementation 'com.formdev:flatlaf-extras:2.4'
|
||||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.1'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
implementation 'org.apache.commons:commons-text:1.9'
|
implementation 'org.apache.commons:commons-text:1.9'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
|
||||||
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
||||||
implementation 'com.android.tools.build:apksig:4.2.1'
|
implementation 'com.android.tools.build:apksig:7.2.2'
|
||||||
implementation 'io.github.hqktech:jdwp:1.0'
|
implementation 'io.github.hqktech:jdwp:1.0'
|
||||||
|
|
||||||
// TODO: Switch back to upstream once this PR gets merged:
|
// TODO: Switch back to upstream once this PR gets merged:
|
||||||
@@ -42,7 +42,8 @@ application {
|
|||||||
mainClass.set('jadx.gui.JadxGUI')
|
mainClass.set('jadx.gui.JadxGUI')
|
||||||
// The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC
|
// The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC
|
||||||
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC',
|
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC',
|
||||||
'-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true']
|
'-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true',
|
||||||
|
'-Djava.util.Arrays.useLegacyMergeSort=true']
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationDistribution.with {
|
applicationDistribution.with {
|
||||||
@@ -138,3 +139,8 @@ task distWinWithJre(type: Zip, dependsOn: ['copyDistWinWithJre']) {
|
|||||||
}
|
}
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task addNewNLSLines(type: JavaExec) {
|
||||||
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
|
mainClass.set('jadx.gui.utils.tools.NLSAddNewLines')
|
||||||
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ public class JadxWrapper {
|
|||||||
return getDecompiler().getJavaNodeByRef(nodeRef);
|
return getDecompiler().getJavaNodeByRef(nodeRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
public @Nullable JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||||
return getDecompiler().getEnclosingNode(codeInfo, pos);
|
return getDecompiler().getEnclosingNode(codeInfo, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
|||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
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 static jadx.api.plugins.input.data.AccessFlagsScope.FIELD;
|
import static jadx.api.plugins.input.data.AccessFlagsScope.FIELD;
|
||||||
import static jadx.api.plugins.input.data.AccessFlagsScope.METHOD;
|
import static jadx.api.plugins.input.data.AccessFlagsScope.METHOD;
|
||||||
@@ -60,7 +61,9 @@ import static jadx.api.plugins.input.insns.Opcode.INVOKE_CUSTOM;
|
|||||||
import static jadx.api.plugins.input.insns.Opcode.INVOKE_CUSTOM_RANGE;
|
import static jadx.api.plugins.input.insns.Opcode.INVOKE_CUSTOM_RANGE;
|
||||||
import static jadx.api.plugins.input.insns.Opcode.INVOKE_POLYMORPHIC;
|
import static jadx.api.plugins.input.insns.Opcode.INVOKE_POLYMORPHIC;
|
||||||
import static jadx.api.plugins.input.insns.Opcode.INVOKE_POLYMORPHIC_RANGE;
|
import static jadx.api.plugins.input.insns.Opcode.INVOKE_POLYMORPHIC_RANGE;
|
||||||
|
import static jadx.api.plugins.input.insns.Opcode.PACKED_SWITCH;
|
||||||
import static jadx.api.plugins.input.insns.Opcode.PACKED_SWITCH_PAYLOAD;
|
import static jadx.api.plugins.input.insns.Opcode.PACKED_SWITCH_PAYLOAD;
|
||||||
|
import static jadx.api.plugins.input.insns.Opcode.SPARSE_SWITCH;
|
||||||
import static jadx.api.plugins.input.insns.Opcode.SPARSE_SWITCH_PAYLOAD;
|
import static jadx.api.plugins.input.insns.Opcode.SPARSE_SWITCH_PAYLOAD;
|
||||||
|
|
||||||
public class Smali {
|
public class Smali {
|
||||||
@@ -288,6 +291,14 @@ public class Smali {
|
|||||||
if (codeReader.getDebugInfo() != null) {
|
if (codeReader.getDebugInfo() != null) {
|
||||||
formatDbgInfo(codeReader.getDebugInfo(), line);
|
formatDbgInfo(codeReader.getDebugInfo(), line);
|
||||||
}
|
}
|
||||||
|
// first pass to fill payload offsets for switch instructions
|
||||||
|
codeReader.visitInstructions(insn -> {
|
||||||
|
Opcode opcode = insn.getOpcode();
|
||||||
|
if (opcode == PACKED_SWITCH || opcode == SPARSE_SWITCH) {
|
||||||
|
insn.decode();
|
||||||
|
line.addPayloadOffset(insn.getOffset(), insn.getTarget());
|
||||||
|
}
|
||||||
|
});
|
||||||
codeReader.visitInstructions(insn -> {
|
codeReader.visitInstructions(insn -> {
|
||||||
InsnNode node = decodeInsn(insn, line);
|
InsnNode node = decodeInsn(insn, line);
|
||||||
nodes.put((long) insn.getOffset(), node);
|
nodes.put((long) insn.getOffset(), node);
|
||||||
@@ -404,7 +415,6 @@ public class Smali {
|
|||||||
line.addTip(target, String.format(FMT_S_SWITCH_TAG, target), "");
|
line.addTip(target, String.format(FMT_S_SWITCH_TAG, target), "");
|
||||||
line.getLineWriter().append(", ").append(String.format(FMT_S_SWITCH, target));
|
line.getLineWriter().append(", ").append(String.format(FMT_S_SWITCH, target));
|
||||||
}
|
}
|
||||||
line.addPayloadOffset(insn.getOffset(), target);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -733,7 +743,7 @@ public class Smali {
|
|||||||
|
|
||||||
ISwitchPayload payload = (ISwitchPayload) insn.getPayload();
|
ISwitchPayload payload = (ISwitchPayload) insn.getPayload();
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
fmtSwitchPayload(insn, FMT_P_SWITCH_CASE, FMT_P_SWITCH_CASE_TAG, line, payload, insn.getOffset());
|
fmtSwitchPayload(insn, FMT_P_SWITCH_CASE, FMT_P_SWITCH_CASE_TAG, line, payload);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -743,7 +753,7 @@ public class Smali {
|
|||||||
|
|
||||||
ISwitchPayload payload = (ISwitchPayload) insn.getPayload();
|
ISwitchPayload payload = (ISwitchPayload) insn.getPayload();
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
fmtSwitchPayload(insn, FMT_S_SWITCH_CASE, FMT_S_SWITCH_CASE_TAG, line, payload, insn.getOffset());
|
fmtSwitchPayload(insn, FMT_S_SWITCH_CASE, FMT_S_SWITCH_CASE_TAG, line, payload);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -755,17 +765,19 @@ public class Smali {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fmtSwitchPayload(InsnData insn, String fmtTarget, String fmtTag, LineInfo line,
|
private void fmtSwitchPayload(InsnData insn, String fmtTarget, String fmtTag, LineInfo line, ISwitchPayload payload) {
|
||||||
ISwitchPayload payload, int curOffset) {
|
|
||||||
int lineStart = getInsnColStart();
|
int lineStart = getInsnColStart();
|
||||||
lineStart += CODE_OFFSET_COLUMN_WIDTH + 1 + 1; // plus 1s for space and the ':'
|
lineStart += CODE_OFFSET_COLUMN_WIDTH + 1 + 1; // plus 1s for space and the ':'
|
||||||
String basicIndent = new String(new byte[lineStart]).replace("\0", " ");
|
String basicIndent = new String(new byte[lineStart]).replace("\0", " ");
|
||||||
String indent = SmaliWriter.INDENT_STR + basicIndent;
|
String indent = SmaliWriter.INDENT_STR + basicIndent;
|
||||||
int[] keys = payload.getKeys();
|
int[] keys = payload.getKeys();
|
||||||
int[] targets = payload.getTargets();
|
int[] targets = payload.getTargets();
|
||||||
int opcodeOffset = line.payloadOffsetMap.get(curOffset);
|
Integer switchOffset = line.payloadOffsetMap.get(insn.getOffset());
|
||||||
|
if (switchOffset == null) {
|
||||||
|
throw new JadxRuntimeException("Unknown switch insn for payload at " + insn.getOffset());
|
||||||
|
}
|
||||||
for (int i = 0; i < keys.length; i++) {
|
for (int i = 0; i < keys.length; i++) {
|
||||||
int target = opcodeOffset + targets[i];
|
int target = switchOffset + targets[i];
|
||||||
line.addInsnLine(insn.getOffset(),
|
line.addInsnLine(insn.getOffset(),
|
||||||
String.format("%scase %d: -> " + fmtTarget, indent, keys[i], target));
|
String.format("%scase %d: -> " + fmtTarget, indent, keys[i], target));
|
||||||
line.addTip(target,
|
line.addTip(target,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class ADB {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (SocketException e) {
|
} catch (SocketException e) {
|
||||||
LOG.error("Aborting readServiceProtocol: socket closed");
|
LOG.warn("Aborting readServiceProtocol: {}", e.toString());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to read readServiceProtocol", e);
|
LOG.error("Failed to read readServiceProtocol", e);
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ public class ADB {
|
|||||||
List<ADBDeviceInfo> deviceInfoList = new ArrayList<>(deviceLines.length);
|
List<ADBDeviceInfo> deviceInfoList = new ArrayList<>(deviceLines.length);
|
||||||
for (String deviceLine : deviceLines) {
|
for (String deviceLine : deviceLines) {
|
||||||
if (!deviceLine.trim().isEmpty()) {
|
if (!deviceLine.trim().isEmpty()) {
|
||||||
deviceInfoList.add(ADBDeviceInfo.make(deviceLine, host, port));
|
deviceInfoList.add(new ADBDeviceInfo(deviceLine, host, port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.onDeviceStatusChange(deviceInfoList);
|
listener.onDeviceStatusChange(deviceInfoList);
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ public class ADBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeviceInfo(ADBDeviceInfo info) {
|
public boolean updateDeviceInfo(ADBDeviceInfo info) {
|
||||||
boolean matched = this.info.serial.equals(info.serial);
|
if (info.getSerial() == null || info.getSerial().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean matched = this.info.getSerial().equals(info.getSerial());
|
||||||
if (matched) {
|
if (matched) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
}
|
}
|
||||||
@@ -47,21 +50,21 @@ public class ADBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSerial() {
|
public String getSerial() {
|
||||||
return info.serial;
|
return info.getSerial();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeForward(String localPort) throws IOException {
|
public boolean removeForward(String localPort) throws IOException {
|
||||||
return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort);
|
return ADB.removeForward(info.getAdbHost(), info.getAdbPort(), info.getSerial(), localPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException {
|
public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException {
|
||||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||||
String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid);
|
String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid);
|
||||||
cmd = String.format("%04x%s", cmd.length(), cmd);
|
cmd = String.format("%04x%s", cmd.length(), cmd);
|
||||||
InputStream inputStream = socket.getInputStream();
|
InputStream inputStream = socket.getInputStream();
|
||||||
OutputStream outputStream = socket.getOutputStream();
|
OutputStream outputStream = socket.getOutputStream();
|
||||||
ForwardResult rst;
|
ForwardResult rst;
|
||||||
if (ADB.setSerial(info.serial, outputStream, inputStream)) {
|
if (ADB.setSerial(info.getSerial(), outputStream, inputStream)) {
|
||||||
outputStream.write(cmd.getBytes());
|
outputStream.write(cmd.getBytes());
|
||||||
if (!ADB.isOkay(inputStream)) {
|
if (!ADB.isOkay(inputStream)) {
|
||||||
rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream));
|
rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream));
|
||||||
@@ -99,9 +102,9 @@ public class ADBDevice {
|
|||||||
*/
|
*/
|
||||||
public int launchApp(String fullAppName) throws IOException, InterruptedException {
|
public int launchApp(String fullAppName) throws IOException, InterruptedException {
|
||||||
byte[] res;
|
byte[] res;
|
||||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||||
String cmd = "am start -D -n " + fullAppName;
|
String cmd = "am start -D -n " + fullAppName;
|
||||||
res = ADB.execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream());
|
res = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -134,13 +137,13 @@ public class ADBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getProp(String entry) throws IOException {
|
public List<String> getProp(String entry) throws IOException {
|
||||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||||
List<String> props = Collections.emptyList();
|
List<String> props = Collections.emptyList();
|
||||||
String cmd = "getprop";
|
String cmd = "getprop";
|
||||||
if (!StringUtils.isEmpty(entry)) {
|
if (!StringUtils.isEmpty(entry)) {
|
||||||
cmd += " " + entry;
|
cmd += " " + entry;
|
||||||
}
|
}
|
||||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||||
socket.getOutputStream(), socket.getInputStream());
|
socket.getOutputStream(), socket.getInputStream());
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
props = new ArrayList<>();
|
props = new ArrayList<>();
|
||||||
@@ -166,9 +169,9 @@ public class ADBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Process> getProcessList(String cmd, int index) throws IOException {
|
private List<Process> getProcessList(String cmd, int index) throws IOException {
|
||||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||||
List<Process> procs = new ArrayList<>();
|
List<Process> procs = new ArrayList<>();
|
||||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||||
socket.getOutputStream(), socket.getInputStream());
|
socket.getOutputStream(), socket.getInputStream());
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
String ps = new String(payload);
|
String ps = new String(payload);
|
||||||
@@ -194,10 +197,10 @@ public class ADBDevice {
|
|||||||
if (this.jdwpListenerSock != null) {
|
if (this.jdwpListenerSock != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
jdwpListenerSock = ADB.connect(this.info.adbHost, this.info.adbPort);
|
jdwpListenerSock = ADB.connect(this.info.getAdbHost(), this.info.getAdbPort());
|
||||||
InputStream inputStream = jdwpListenerSock.getInputStream();
|
InputStream inputStream = jdwpListenerSock.getInputStream();
|
||||||
OutputStream outputStream = jdwpListenerSock.getOutputStream();
|
OutputStream outputStream = jdwpListenerSock.getOutputStream();
|
||||||
if (ADB.setSerial(info.serial, outputStream, inputStream)
|
if (ADB.setSerial(info.getSerial(), outputStream, inputStream)
|
||||||
&& ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) {
|
&& ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) {
|
||||||
Executors.newFixedThreadPool(1).execute(() -> {
|
Executors.newFixedThreadPool(1).execute(() -> {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -244,19 +247,20 @@ public class ADBDevice {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return info.serial.hashCode();
|
return info.getSerial().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof ADBDevice) {
|
if (obj instanceof ADBDevice) {
|
||||||
return ((ADBDevice) obj).getDeviceInfo().serial.equals(info.serial);
|
String otherSerial = ((ADBDevice) obj).getDeviceInfo().getSerial();
|
||||||
|
return otherSerial.equals(info.getSerial());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return info.allInfo;
|
return info.getAllInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,91 @@
|
|||||||
package jadx.gui.device.protocol;
|
package jadx.gui.device.protocol;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.core.utils.log.LogUtils;
|
||||||
|
|
||||||
public class ADBDeviceInfo {
|
public class ADBDeviceInfo {
|
||||||
public String adbHost;
|
private static final Logger LOG = LoggerFactory.getLogger(ADBDeviceInfo.class);
|
||||||
public int adbPort;
|
private final String adbHost;
|
||||||
public String serial;
|
private final int adbPort;
|
||||||
public String state;
|
private final String serial;
|
||||||
public String model;
|
private final String state;
|
||||||
public String allInfo;
|
private final String model;
|
||||||
|
private final String allInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the device info property values like "device" "model" "product" or "transport_id"
|
||||||
|
*/
|
||||||
|
private final Map<String, String> propertiesMap = new TreeMap<>();
|
||||||
|
|
||||||
|
ADBDeviceInfo(String info, String host, int port) {
|
||||||
|
String[] infoFields = info.trim().split("\\s+");
|
||||||
|
allInfo = String.join(" ", infoFields);
|
||||||
|
if (infoFields.length > 2) {
|
||||||
|
serial = infoFields[0];
|
||||||
|
state = infoFields[1];
|
||||||
|
|
||||||
|
for (int i = 2; i < infoFields.length; i++) {
|
||||||
|
String field = infoFields[i];
|
||||||
|
int idx = field.indexOf(':');
|
||||||
|
if (idx > 0) {
|
||||||
|
String key = field.substring(0, idx);
|
||||||
|
String value = field.substring(idx + 1);
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
propertiesMap.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model = propertiesMap.getOrDefault("model", serial);
|
||||||
|
} else {
|
||||||
|
LOG.error("Unable to extract device information from {}", LogUtils.escape(info));
|
||||||
|
serial = "";
|
||||||
|
state = "unknown";
|
||||||
|
model = "unknown";
|
||||||
|
}
|
||||||
|
adbHost = host;
|
||||||
|
adbPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isOnline() {
|
public boolean isOnline() {
|
||||||
return state.equals("device");
|
return state.equals("device");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAdbHost() {
|
||||||
|
return adbHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAdbPort() {
|
||||||
|
return adbPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerial() {
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAllInfo() {
|
||||||
|
return allInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty(String key) {
|
||||||
|
return this.propertiesMap.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return allInfo;
|
return allInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ADBDeviceInfo make(String info, String host, int port) {
|
|
||||||
ADBDeviceInfo deviceInfo = new ADBDeviceInfo();
|
|
||||||
String[] infoFields = info.trim().split("\\s+");
|
|
||||||
deviceInfo.allInfo = String.join(" ", infoFields);
|
|
||||||
if (infoFields.length > 2) {
|
|
||||||
deviceInfo.serial = infoFields[0];
|
|
||||||
deviceInfo.state = infoFields[1];
|
|
||||||
}
|
|
||||||
int pos = info.indexOf("model:");
|
|
||||||
if (pos != -1) {
|
|
||||||
int spacePos = info.indexOf(" ", pos);
|
|
||||||
if (spacePos != -1) {
|
|
||||||
deviceInfo.model = info.substring(pos + "model:".length(), spacePos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (deviceInfo.model == null || deviceInfo.model.equals("")) {
|
|
||||||
deviceInfo.model = deviceInfo.serial;
|
|
||||||
}
|
|
||||||
deviceInfo.adbHost = host;
|
|
||||||
deviceInfo.adbPort = port;
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,8 +145,8 @@ public class BackgroundExecutor {
|
|||||||
task.onDone(this);
|
task.onDone(this);
|
||||||
// treat UI task operations as part of the task to not mix with others
|
// treat UI task operations as part of the task to not mix with others
|
||||||
UiUtils.uiRunAndWait(() -> {
|
UiUtils.uiRunAndWait(() -> {
|
||||||
task.onFinish(this);
|
|
||||||
progressPane.setVisible(false);
|
progressPane.setVisible(false);
|
||||||
|
task.onFinish(this);
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
taskComplete(id);
|
taskComplete(id);
|
||||||
@@ -190,13 +190,21 @@ public class BackgroundExecutor {
|
|||||||
performCancel(executor);
|
performCancel(executor);
|
||||||
return cancelStatus;
|
return cancelStatus;
|
||||||
}
|
}
|
||||||
updateProgress(executor);
|
if (k < 10) {
|
||||||
k++;
|
// faster update for short tasks
|
||||||
Thread.sleep(k < 10 ? 200 : 1000); // faster update for short tasks
|
Thread.sleep(200);
|
||||||
if (jobsCount == 1 && k == 3) {
|
if (k == 5) {
|
||||||
|
updateProgress(executor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateProgress(executor);
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
if (jobsCount == 1 && k == 5) {
|
||||||
// small delay before show progress to reduce blinking on short tasks
|
// small delay before show progress to reduce blinking on short tasks
|
||||||
progressPane.changeVisibility(this, true);
|
progressPane.changeVisibility(this, true);
|
||||||
}
|
}
|
||||||
|
k++;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.debug("Task wait interrupted");
|
LOG.debug("Task wait interrupted");
|
||||||
@@ -210,7 +218,7 @@ public class BackgroundExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(ThreadPoolExecutor executor) {
|
private void updateProgress(ThreadPoolExecutor executor) {
|
||||||
Consumer<ITaskProgress> onProgressListener = task.getOnProgressListener();
|
Consumer<ITaskProgress> onProgressListener = task.getProgressListener();
|
||||||
ITaskProgress taskProgress = task.getTaskProgress();
|
ITaskProgress taskProgress = task.getTaskProgress();
|
||||||
if (taskProgress == null) {
|
if (taskProgress == null) {
|
||||||
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||||
@@ -231,13 +239,16 @@ public class BackgroundExecutor {
|
|||||||
// force termination
|
// force termination
|
||||||
task.cancel();
|
task.cancel();
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
if (executor.awaitTermination(2, TimeUnit.SECONDS)) {
|
int cancelTimeout = task.getCancelTimeoutMS();
|
||||||
LOG.debug("Task cancel complete");
|
if (cancelTimeout != 0) {
|
||||||
return;
|
if (executor.awaitTermination(cancelTimeout, TimeUnit.MILLISECONDS)) {
|
||||||
|
LOG.debug("Task cancel complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Forcing tasks cancel");
|
LOG.debug("Forcing tasks cancel");
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
boolean complete = executor.awaitTermination(task.getShutdownTimeoutMS(), TimeUnit.MILLISECONDS);
|
||||||
LOG.debug("Forced task cancel status: {}",
|
LOG.debug("Forced task cancel status: {}",
|
||||||
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
||||||
}
|
}
|
||||||
@@ -301,5 +312,13 @@ public class BackgroundExecutor {
|
|||||||
public long getTime() {
|
public long getTime() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TaskWorker{status=" + status
|
||||||
|
+ ", jobsCount=" + jobsCount
|
||||||
|
+ ", jobsComplete=" + jobsComplete
|
||||||
|
+ ", time=" + time + '}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,12 @@ public interface Cancelable {
|
|||||||
boolean isCanceled();
|
boolean isCanceled();
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
|
default int getCancelTimeoutMS() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getShutdownTimeoutMS() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public interface IBackgroundTask extends Cancelable {
|
|||||||
/**
|
/**
|
||||||
* Return progress notifications listener (use executor tick rate and thread) (Optional)
|
* Return progress notifications listener (use executor tick rate and thread) (Optional)
|
||||||
*/
|
*/
|
||||||
default @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
default @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class QuarkManager {
|
|||||||
private void loadReport() {
|
private void loadReport() {
|
||||||
try {
|
try {
|
||||||
QuarkReportNode quarkNode = new QuarkReportNode(reportFile);
|
QuarkReportNode quarkNode = new QuarkReportNode(reportFile);
|
||||||
JRoot root = mainWindow.getCacheObject().getJRoot();
|
JRoot root = mainWindow.getTreeRoot();
|
||||||
root.replaceCustomNode(quarkNode);
|
root.replaceCustomNode(quarkNode);
|
||||||
root.update();
|
root.update();
|
||||||
mainWindow.reloadTree();
|
mainWindow.reloadTree();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import jadx.core.utils.Utils;
|
|||||||
|
|
||||||
@SuppressWarnings("MemberName")
|
@SuppressWarnings("MemberName")
|
||||||
public class QuarkReportData {
|
public class QuarkReportData {
|
||||||
|
|
||||||
public static class Crime {
|
public static class Crime {
|
||||||
public String crime;
|
public String crime;
|
||||||
public String confidence;
|
public String confidence;
|
||||||
@@ -18,6 +19,23 @@ public class QuarkReportData {
|
|||||||
List<Method> native_api;
|
List<Method> native_api;
|
||||||
List<JsonElement> combination;
|
List<JsonElement> combination;
|
||||||
List<Map<String, InvokePlace>> register;
|
List<Map<String, InvokePlace>> register;
|
||||||
|
|
||||||
|
public int parseConfidence() {
|
||||||
|
return Integer.parseInt(confidence.replace("%", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuffer sb = new StringBuffer("Crime{");
|
||||||
|
sb.append("crime='").append(crime).append('\'');
|
||||||
|
sb.append(", confidence='").append(confidence).append('\'');
|
||||||
|
sb.append(", permissions=").append(permissions);
|
||||||
|
sb.append(", native_api=").append(native_api);
|
||||||
|
sb.append(", combination=").append(combination);
|
||||||
|
sb.append(", register=").append(register);
|
||||||
|
sb.append('}');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Method {
|
public static class Method {
|
||||||
@@ -46,4 +64,22 @@ public class QuarkReportData {
|
|||||||
String threat_level;
|
String threat_level;
|
||||||
int total_score;
|
int total_score;
|
||||||
List<Crime> crimes;
|
List<Crime> crimes;
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (crimes == null) {
|
||||||
|
throw new RuntimeException("Invalid data: \"crimes\" list missing");
|
||||||
|
}
|
||||||
|
for (Crime crime : crimes) {
|
||||||
|
if (crime.confidence == null) {
|
||||||
|
throw new RuntimeException("Confidence value missing: " + crime);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
crime.parseConfidence();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Invalid crime entry: " + crime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.gui.plugins.quark;
|
package jadx.gui.plugins.quark;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@@ -33,12 +34,12 @@ public class QuarkReportNode extends JNode {
|
|||||||
|
|
||||||
private static final ImageIcon ICON = UiUtils.openSvgIcon("ui/quark");
|
private static final ImageIcon ICON = UiUtils.openSvgIcon("ui/quark");
|
||||||
|
|
||||||
private final Path apkFile;
|
private final Path reportFile;
|
||||||
|
|
||||||
private ICodeInfo errorContent;
|
private ICodeInfo errorContent;
|
||||||
|
|
||||||
public QuarkReportNode(Path apkFile) {
|
public QuarkReportNode(Path reportFile) {
|
||||||
this.apkFile = apkFile;
|
this.reportFile = reportFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,7 +60,11 @@ public class QuarkReportNode extends JNode {
|
|||||||
@Override
|
@Override
|
||||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||||
try {
|
try {
|
||||||
QuarkReportData data = GSON.fromJson(Files.newBufferedReader(apkFile), QuarkReportData.class);
|
QuarkReportData data;
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(reportFile)) {
|
||||||
|
data = GSON.fromJson(reader, QuarkReportData.class);
|
||||||
|
}
|
||||||
|
data.validate();
|
||||||
return new QuarkReportPanel(tabbedPane, this, data);
|
return new QuarkReportPanel(tabbedPane, this, data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Quark report parse error", e);
|
LOG.error("Quark report parse error", e);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class QuarkReportPanel extends ContentPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void prepareData() {
|
private void prepareData() {
|
||||||
data.crimes.sort(Comparator.comparingInt(c -> -Integer.parseInt(c.confidence.replace("%", ""))));
|
data.crimes.sort(Comparator.comparingInt(c -> -c.parseConfidence()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initUI() {
|
private void initUI() {
|
||||||
@@ -290,7 +290,7 @@ public class QuarkReportPanel extends ContentPanel {
|
|||||||
}
|
}
|
||||||
return new MethodTreeNode(javaMethod);
|
return new MethodTreeNode(javaMethod);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to parse method descriptor string", e);
|
LOG.error("Failed to parse method descriptor string: {}", descr, e);
|
||||||
return new TextTreeNode(descr);
|
return new TextTreeNode(descr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user