Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6844a46c93 | |||
| e9e45707da | |||
| b9d02ff4c4 | |||
| 29b64300bc | |||
| 777355e86e | |||
| 620a177ce8 | |||
| 683c2dfbeb | |||
| 266cbcc6f4 | |||
| 8a45602ae6 | |||
| 711419a797 | |||
| 603f3057eb | |||
| fa6fc1f871 | |||
| 49fa320989 | |||
| 2f301bf150 | |||
| b4892ce17f | |||
| 151c171616 | |||
| 79477a2de3 | |||
| 78aadda931 | |||
| b50706505f | |||
| 9114821fb1 | |||
| 1195582da8 | |||
| 258987b0ff | |||
| a6a734c70d | |||
| d6c23a2a9b | |||
| db028904d7 | |||
| 63a571306c | |||
| bc4db61e25 | |||
| c7e6e28830 | |||
| 1d7b6fdb2c | |||
| ce5d8eeff8 | |||
| 894e0e6132 | |||
| 127f0ecf3f | |||
| cf7767e702 | |||
| e0aedc7949 | |||
| bad78de74c | |||
| 12df8a169f | |||
| 15c9d33339 | |||
| 7e0fafbaf1 | |||
| 57b9c1dd7a | |||
| 8ba0c17259 | |||
| cd32151083 | |||
| 75b52d672e | |||
| 11d04508f7 | |||
| e641b773b5 | |||
| 6e5899c654 | |||
| c66ffaa7f9 | |||
| 5193c6a5d8 | |||
| e7212af547 | |||
| 3ca1357af4 | |||
| 90e95213e4 | |||
| ae2d4da585 | |||
| 691d5cd1e6 | |||
| 58a46c6417 | |||
| d3f6160e62 | |||
| 03e4afb12f | |||
| 6802f6028e | |||
| 5ca61cfe18 | |||
| 32d55b48f2 | |||
| ab4b6f9e54 | |||
| 9100ad1220 | |||
| 8b4f8fb572 |
@@ -57,7 +57,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
||||
uses: oracle-actions/setup-java@v1
|
||||
with:
|
||||
release: 18
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
@@ -6,5 +6,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
@@ -102,6 +102,7 @@ options:
|
||||
--add-debug-lines - add comments with debug line numbers if available
|
||||
--no-inline-anonymous - disable anonymous classes inline
|
||||
--no-inline-methods - disable methods inline
|
||||
--no-finally - don't extract finally block
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||
@@ -116,6 +117,10 @@ options:
|
||||
'ignore' - don't read and don't save
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--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
|
||||
--rename-flags - fix options (comma-separated list of):
|
||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.8.0'
|
||||
id 'com.github.ben-manes.versions' version '0.43.0'
|
||||
id 'com.diffplug.spotless' version '6.11.0'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -26,16 +26,16 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.3'
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.6.1'
|
||||
testImplementation 'org.mockito:mockito-core:4.8.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
|
||||
|
||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -205,6 +205,12 @@ set -- \
|
||||
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.
|
||||
#
|
||||
# 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
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
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
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies {
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
implementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -21,6 +21,7 @@ import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@@ -88,6 +89,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||
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")
|
||||
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")
|
||||
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(
|
||||
names = { "--use-kotlin-methods-for-var-names" },
|
||||
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.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
@@ -270,6 +285,7 @@ public class JadxCLIArgs {
|
||||
args.setInsertDebugLines(addDebugLines);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setInlineMethods(inlineMethods);
|
||||
args.setExtractFinally(extractFinally);
|
||||
args.setRenameFlags(renameFlags);
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
@@ -350,6 +366,10 @@ public class JadxCLIArgs {
|
||||
return inlineMethods;
|
||||
}
|
||||
|
||||
public boolean isExtractFinally() {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
@@ -378,6 +398,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||
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> {
|
||||
@Override
|
||||
public DecompilationMode convert(String value) {
|
||||
|
||||
@@ -5,11 +5,11 @@ plugins {
|
||||
dependencies {
|
||||
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'
|
||||
|
||||
// TODO: move resources decoding to separate plugin module
|
||||
implementation 'com.android.tools.build:aapt2-proto:7.2.1-7984345'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.2' // forcing latest version
|
||||
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.8' // forcing latest version
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
@@ -19,7 +19,7 @@ dependencies {
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||
|
||||
testImplementation 'org.eclipse.jdt:ecj:3.30.0'
|
||||
testImplementation 'org.eclipse.jdt:ecj:3.31.0'
|
||||
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
@@ -72,6 +73,7 @@ public class JadxArgs {
|
||||
private File deobfuscationMapFile = null;
|
||||
|
||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
@@ -369,6 +371,14 @@ public class JadxArgs {
|
||||
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
||||
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||
this.resourceNameSource = resourceNameSource;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
@@ -540,6 +550,7 @@ public class JadxArgs {
|
||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||
+ inlineAnonymousClasses + inlineMethods
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||
+ resourceNameSource
|
||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
@@ -564,6 +575,7 @@ public class JadxArgs {
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -191,7 +191,6 @@ public class Jadx {
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
@@ -215,9 +214,6 @@ public class Jadx {
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public class ClassGen {
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !cls.isEnum()) {
|
||||
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
@@ -322,19 +322,25 @@ public class ClassGen {
|
||||
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
||||
return false;
|
||||
}
|
||||
if (mth.getUseIn().isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
try {
|
||||
if (mth.getUseIn().isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||
.collect(Collectors.toList());
|
||||
if (useInCompleted.isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
// check failed => keep method
|
||||
mth.addWarnComment("Failed to check method usage", e);
|
||||
return false;
|
||||
}
|
||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||
.collect(Collectors.toList());
|
||||
if (useInCompleted.isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isMethodsPresents() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ConditionGen extends InsnGen {
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||
private final Queue<IfCondition> stack = new ArrayDeque<>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
|
||||
@@ -828,7 +828,11 @@ public class InsnGen {
|
||||
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
|
||||
code.add(callMth.getName());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
if (callMthNode != null) {
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
}
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ public class SimpleModeHelper {
|
||||
startLabel.set(block.getId());
|
||||
} else if (predsCount == 1 && prev != null) {
|
||||
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)) {
|
||||
endGoto.set(prev.getId());
|
||||
}
|
||||
|
||||
@@ -428,7 +428,9 @@ public class Deobfuscator {
|
||||
return "Enum";
|
||||
}
|
||||
String result = "";
|
||||
if (cls.getAccessFlags().isAbstract()) {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
result += "Interface";
|
||||
} else if (cls.getAccessFlags().isAbstract()) {
|
||||
result += "Abstract";
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum AFlag {
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||
REMOVE, // can be completely removed
|
||||
REMOVE_SUPER_CLASS, // don't add super class
|
||||
|
||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||
|
||||
|
||||
@@ -91,6 +91,19 @@ public class LoopInfo {
|
||||
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
|
||||
public String toString() {
|
||||
return "LOOP:" + id + ": " + start + "->" + end;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
public class PhiListAttr implements IJadxAttribute {
|
||||
|
||||
private final List<PhiInsn> list = new LinkedList<>();
|
||||
private final List<PhiInsn> list = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public AType<PhiListAttr> getAttrType() {
|
||||
|
||||
@@ -19,29 +19,59 @@ import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Offset in methods bytecode
|
||||
*/
|
||||
private final int startOffset;
|
||||
|
||||
private final List<InsnNode> instructions = new ArrayList<>(2);
|
||||
|
||||
private List<BlockNode> predecessors = new ArrayList<>(1);
|
||||
private List<BlockNode> successors = new ArrayList<>(1);
|
||||
private List<BlockNode> cleanSuccessors;
|
||||
|
||||
// all dominators
|
||||
/**
|
||||
* All dominators, excluding self
|
||||
*/
|
||||
private BitSet doms = EmptyBitSet.EMPTY;
|
||||
// dominance frontier
|
||||
|
||||
/**
|
||||
* Dominance frontier
|
||||
*/
|
||||
private BitSet domFrontier;
|
||||
// immediate dominator
|
||||
|
||||
/**
|
||||
* Immediate dominator
|
||||
*/
|
||||
private BlockNode idom;
|
||||
// blocks on which dominates this block
|
||||
|
||||
/**
|
||||
* Blocks on which dominates this block
|
||||
*/
|
||||
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.startOffset = offset;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
public int getCId() {
|
||||
return cid;
|
||||
}
|
||||
|
||||
void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -188,12 +218,12 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
return false;
|
||||
}
|
||||
BlockNode other = (BlockNode) obj;
|
||||
return id == other.id && startOffset == other.startOffset;
|
||||
return cid == other.cid && startOffset == other.startOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull BlockNode o) {
|
||||
return Integer.compare(id, o.id);
|
||||
return Integer.compare(cid, o.cid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -203,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
|
||||
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +469,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
|
||||
public void addField(FieldNode fld) {
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
fields = new ArrayList<>(1);
|
||||
}
|
||||
fields.add(fld);
|
||||
}
|
||||
|
||||
@@ -657,6 +660,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return contains(AType.ANONYMOUS_CLASS);
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return contains(AFlag.SYNTHETIC);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return parentClass != this;
|
||||
}
|
||||
@@ -798,7 +805,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
|
||||
public void addCodegenDep(ClassNode dep) {
|
||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||
if (!codegenDeps.contains(dep)) {
|
||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalDepsCount() {
|
||||
|
||||
@@ -57,6 +57,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return accFlags.isStatic();
|
||||
}
|
||||
|
||||
public boolean isInstance() {
|
||||
return !accFlags.isStatic();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return fieldInfo.getName();
|
||||
}
|
||||
@@ -65,6 +69,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return fieldInfo.getAlias();
|
||||
}
|
||||
|
||||
public void rename(String alias) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -73,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getTopParentClass() {
|
||||
return parentClass.getTopParentClass();
|
||||
}
|
||||
|
||||
public List<MethodNode> getUseIn() {
|
||||
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() {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
|
||||
@@ -61,6 +61,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
private List<RegisterArg> argsList;
|
||||
private InsnNode[] instructions;
|
||||
private List<BlockNode> blocks;
|
||||
private int blocksMaxCId;
|
||||
private BlockNode enterBlock;
|
||||
private BlockNode exitBlock;
|
||||
private List<SSAVar> sVars;
|
||||
@@ -318,6 +319,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
|
||||
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() {
|
||||
|
||||
@@ -156,7 +156,7 @@ public final class IfCondition extends AttrNode {
|
||||
return i;
|
||||
}
|
||||
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
|
||||
cond = not(new IfCondition(c.invert()));
|
||||
cond = new IfCondition(Mode.NOT, Collections.singletonList(new IfCondition(c.invert())));
|
||||
} else {
|
||||
c.normalize();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ public final class LoopRegion extends ConditionRegion {
|
||||
return header;
|
||||
}
|
||||
|
||||
public boolean isEndless() {
|
||||
return header == null;
|
||||
}
|
||||
|
||||
public IRegion getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ public class TryCatchBlockAttr implements IJadxAttribute {
|
||||
return throwFound;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<ExceptionHandler> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@@ -62,45 +62,47 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
|| insn.getResult() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SSAVar sVar = insn.getResult().getSVar();
|
||||
InsnArg constArg;
|
||||
Runnable onSuccess = null;
|
||||
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
||||
constArg = insn.getArg(0);
|
||||
if (!constArg.isLiteral()) {
|
||||
switch (insn.getType()) {
|
||||
case CONST:
|
||||
case MOVE: {
|
||||
constArg = insn.getArg(0);
|
||||
if (!constArg.isLiteral()) {
|
||||
return;
|
||||
}
|
||||
long lit = ((LiteralArg) constArg).getLiteral();
|
||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
||||
// all usages forbids inlining
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONST_STR: {
|
||||
String s = ((ConstStringNode) insn).getString();
|
||||
FieldNode f = mth.getParentClass().getConstField(s);
|
||||
if (f == null) {
|
||||
InsnNode copy = insn.copyWithoutResult();
|
||||
constArg = InsnArg.wrapArg(copy);
|
||||
} else {
|
||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||
constArg = InsnArg.wrapArg(constGet);
|
||||
constArg.setType(ArgType.STRING);
|
||||
onSuccess = () -> f.addUseIn(mth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONST_CLASS: {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||
constArg.setType(ArgType.CLASS);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
long lit = ((LiteralArg) constArg).getLiteral();
|
||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
||||
// all usages forbids inlining
|
||||
return;
|
||||
}
|
||||
} else if (insnType == InsnType.CONST_STR) {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
String s = ((ConstStringNode) insn).getString();
|
||||
FieldNode f = mth.getParentClass().getConstField(s);
|
||||
if (f == null) {
|
||||
InsnNode copy = insn.copyWithoutResult();
|
||||
constArg = InsnArg.wrapArg(copy);
|
||||
} else {
|
||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||
constArg = InsnArg.wrapArg(constGet);
|
||||
constArg.setType(ArgType.STRING);
|
||||
onSuccess = () -> f.addUseIn(mth);
|
||||
}
|
||||
} else if (insnType == InsnType.CONST_CLASS) {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||
constArg.setType(ArgType.CLASS);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// all check passed, run replace
|
||||
|
||||
@@ -100,7 +100,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
if (insnArr == null) {
|
||||
return;
|
||||
}
|
||||
BlockNode block = new BlockNode(0, 0);
|
||||
BlockNode block = new BlockNode(0, 0, 0);
|
||||
List<InsnNode> insnList = block.getInstructions();
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null) {
|
||||
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.add("color=red,");
|
||||
}
|
||||
dot.add("label=\"{");
|
||||
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
|
||||
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
|
||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||
if (!attrs.isEmpty()) {
|
||||
dot.add('|').add(attrs);
|
||||
@@ -230,10 +230,10 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
if (PRINT_DOMINATORS) {
|
||||
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())) {
|
||||
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) {
|
||||
String name;
|
||||
if (c instanceof BlockNode) {
|
||||
name = "Node_" + ((BlockNode) c).getId();
|
||||
name = "Node_" + ((BlockNode) c).getCId();
|
||||
} else if (c instanceof IBlock) {
|
||||
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
||||
} else {
|
||||
|
||||
@@ -37,9 +37,13 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockInsnPair;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
@@ -47,6 +51,7 @@ import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.InsnUtils.checkInsnType;
|
||||
import static jadx.core.utils.InsnUtils.getSingleArg;
|
||||
@@ -55,8 +60,16 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class },
|
||||
runBefore = { ExtractFieldInit.class }
|
||||
runAfter = {
|
||||
CodeShrinkVisitor.class, // all possible instructions already inlined
|
||||
ModVisitor.class,
|
||||
ReSugarCode.class,
|
||||
IfRegionVisitor.class, // ternary operator inlined
|
||||
CheckRegions.class // regions processing finished
|
||||
},
|
||||
runBefore = {
|
||||
ExtractFieldInit.class
|
||||
}
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@@ -81,82 +94,67 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
boolean converted;
|
||||
try {
|
||||
converted = convertToEnum(cls);
|
||||
} catch (Exception e) {
|
||||
cls.addWarnComment("Enum visitor error", e);
|
||||
converted = false;
|
||||
}
|
||||
if (!converted) {
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (accessFlags.isEnum()) {
|
||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed");
|
||||
if (cls.isEnum()) {
|
||||
boolean converted;
|
||||
try {
|
||||
converted = convertToEnum(cls);
|
||||
} catch (Exception e) {
|
||||
cls.addWarnComment("Enum visitor error", e);
|
||||
converted = false;
|
||||
}
|
||||
if (!converted) {
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (accessFlags.isEnum()) {
|
||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||
cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean convertToEnum(ClassNode cls) {
|
||||
if (!cls.isEnum()) {
|
||||
return false;
|
||||
ArgType superType = cls.getSuperClass();
|
||||
if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
cls.add(AFlag.REMOVE_SUPER_CLASS);
|
||||
}
|
||||
MethodNode classInitMth = cls.getClassInitMth();
|
||||
if (classInitMth == null) {
|
||||
cls.addWarnComment("Enum class init method not found");
|
||||
return false;
|
||||
}
|
||||
if (classInitMth.getBasicBlocks().isEmpty()) {
|
||||
Region staticRegion = classInitMth.getRegion();
|
||||
if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
|
||||
// search "$VALUES" field (holds all enum values)
|
||||
List<FieldNode> valuesCandidates = cls.getFields().stream()
|
||||
.filter(f -> f.getAccessFlags().isStatic())
|
||||
.filter(f -> f.getType().isArray())
|
||||
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (valuesCandidates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
||||
if (valuesOpt.isPresent()) {
|
||||
valuesCandidates.clear();
|
||||
valuesCandidates.add(valuesOpt.get());
|
||||
// collect blocks on linear part of static method (ignore branching on method end)
|
||||
List<BlockNode> staticBlocks = new ArrayList<>();
|
||||
for (IContainer subBlock : staticRegion.getSubBlocks()) {
|
||||
if (subBlock instanceof BlockNode) {
|
||||
staticBlocks.add((BlockNode) subBlock);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valuesCandidates.size() != 1) {
|
||||
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
||||
if (staticBlocks.isEmpty()) {
|
||||
cls.addWarnComment("Unexpected branching in enum static init block");
|
||||
return false;
|
||||
}
|
||||
FieldNode valuesField = valuesCandidates.get(0);
|
||||
List<InsnNode> toRemove = new ArrayList<>();
|
||||
|
||||
// search "$VALUES" array init and collect enum fields
|
||||
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
|
||||
if (valuesInitPair == null) {
|
||||
EnumData data = new EnumData(cls, classInitMth, staticBlocks);
|
||||
if (!searchValuesField(data)) {
|
||||
return false;
|
||||
}
|
||||
BlockNode staticBlock = valuesInitPair.getBlock();
|
||||
InsnNode valuesInitInsn = valuesInitPair.getInsn();
|
||||
|
||||
List<EnumField> enumFields = null;
|
||||
InsnArg arrArg = valuesInitInsn.getArg(0);
|
||||
InsnArg arrArg = data.valuesInitInsn.getArg(0);
|
||||
if (arrArg.isInsnWrap()) {
|
||||
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
||||
enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
||||
enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
|
||||
}
|
||||
if (enumFields == null) {
|
||||
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
|
||||
return false;
|
||||
}
|
||||
toRemove.add(valuesInitInsn);
|
||||
data.toRemove.add(data.valuesInitInsn);
|
||||
|
||||
// all checks complete, perform transform
|
||||
EnumClassAttr attr = new EnumClassAttr(enumFields);
|
||||
@@ -176,59 +174,92 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
fieldNode.getFieldInfo().setAlias(name);
|
||||
}
|
||||
fieldNode.add(AFlag.DONT_GENERATE);
|
||||
processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove);
|
||||
processConstructorInsn(data, enumField, classInitMth);
|
||||
}
|
||||
valuesField.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove);
|
||||
data.valuesField.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove);
|
||||
if (classInitMth.countInsns() == 0) {
|
||||
classInitMth.add(AFlag.DONT_GENERATE);
|
||||
} else if (!toRemove.isEmpty()) {
|
||||
} else if (!data.toRemove.isEmpty()) {
|
||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||
}
|
||||
removeEnumMethods(cls, clsType, valuesField);
|
||||
removeEnumMethods(cls, data.valuesField);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth,
|
||||
BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
ClassInfo enumClsInfo = co.getClassType();
|
||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
||||
if (enumCls != null) {
|
||||
processEnumCls(cls, enumField, enumCls);
|
||||
/**
|
||||
* Search "$VALUES" field (holds all enum values)
|
||||
*/
|
||||
private boolean searchValuesField(EnumData data) {
|
||||
ArgType clsType = data.cls.getClassInfo().getType();
|
||||
List<FieldNode> valuesCandidates = data.cls.getFields().stream()
|
||||
.filter(f -> f.getAccessFlags().isStatic())
|
||||
.filter(f -> f.getType().isArray())
|
||||
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (valuesCandidates.isEmpty()) {
|
||||
data.cls.addWarnComment("$VALUES field not found");
|
||||
return false;
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
||||
if (valuesOpt.isPresent()) {
|
||||
valuesCandidates.clear();
|
||||
valuesCandidates.add(valuesOpt.get());
|
||||
}
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
|
||||
if (valuesCandidates.size() != 1) {
|
||||
data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
||||
return false;
|
||||
}
|
||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||
data.valuesField = valuesCandidates.get(0);
|
||||
|
||||
// search "$VALUES" array init and collect enum fields
|
||||
BlockInsnPair valuesInitPair = getValuesInitInsn(data);
|
||||
if (valuesInitPair == null) {
|
||||
return false;
|
||||
}
|
||||
data.valuesInitInsn = valuesInitPair.getInsn();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
ClassInfo enumClsInfo = co.getClassType();
|
||||
if (!enumClsInfo.equals(data.cls.getClassInfo())) {
|
||||
ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo);
|
||||
if (enumCls != null) {
|
||||
processEnumCls(data.cls, enumField, enumCls);
|
||||
}
|
||||
}
|
||||
MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth());
|
||||
if (ctrMth != null) {
|
||||
markArgsForSkip(ctrMth);
|
||||
}
|
||||
RegisterArg coResArg = co.getResult();
|
||||
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
|
||||
toRemove.add(co);
|
||||
data.toRemove.add(co);
|
||||
} else {
|
||||
// constructor result used in other places -> replace constructor with enum field get (SGET)
|
||||
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
|
||||
enumGet.setResult(coResArg.duplicate());
|
||||
BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet);
|
||||
BlockUtils.replaceInsn(classInitMth, co, enumGet);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock,
|
||||
InsnNode wrappedInsn, List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) {
|
||||
switch (wrappedInsn.getType()) {
|
||||
case FILLED_NEW_ARRAY:
|
||||
return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove);
|
||||
return extractEnumFieldsFromFilledArray(enumData, wrappedInsn);
|
||||
|
||||
case INVOKE:
|
||||
// handle redirection of values array fill (added in java 15)
|
||||
return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove);
|
||||
return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn);
|
||||
|
||||
case NEW_ARRAY:
|
||||
InsnArg arg = wrappedInsn.getArg(0);
|
||||
@@ -243,10 +274,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock,
|
||||
InvokeNode invokeNode, List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) {
|
||||
MethodInfo callMth = invokeNode.getCallMth();
|
||||
MethodNode valuesMth = cls.root().resolveMethod(callMth);
|
||||
MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth);
|
||||
if (valuesMth == null || valuesMth.isVoidReturn()) {
|
||||
return null;
|
||||
}
|
||||
@@ -256,16 +286,16 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (wrappedInsn == null) {
|
||||
return null;
|
||||
}
|
||||
List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
||||
List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
|
||||
if (enumFields != null) {
|
||||
valuesMth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
return enumFields;
|
||||
}
|
||||
|
||||
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
|
||||
FieldInfo searchField = valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
|
||||
private BlockInsnPair getValuesInitInsn(EnumData data) {
|
||||
FieldInfo searchField = data.valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : data.staticBlocks) {
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
|
||||
@@ -279,50 +309,49 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock,
|
||||
List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) {
|
||||
List<EnumField> enumFields = new ArrayList<>();
|
||||
for (InsnArg arg : arrFillInsn.getArguments()) {
|
||||
EnumField field = null;
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove);
|
||||
field = processEnumFieldByWrappedInsn(enumData, wrappedInsn);
|
||||
} else if (arg.isRegister()) {
|
||||
field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove);
|
||||
field = processEnumFieldByRegister(enumData, (RegisterArg) arg);
|
||||
}
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
enumFields.add(field);
|
||||
}
|
||||
toRemove.add(arrFillInsn);
|
||||
enumData.toRemove.add(arrFillInsn);
|
||||
return enumFields;
|
||||
}
|
||||
|
||||
private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) {
|
||||
if (wrappedInsn.getType() == InsnType.SGET) {
|
||||
return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove);
|
||||
return processEnumFieldByField(data, wrappedInsn);
|
||||
}
|
||||
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
|
||||
if (constructorInsn != null) {
|
||||
FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset());
|
||||
cls.addField(enumFieldNode);
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn);
|
||||
FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset());
|
||||
data.cls.addField(enumFieldNode);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) {
|
||||
if (sgetInsn.getType() != InsnType.SGET) {
|
||||
return null;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
|
||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
||||
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||
if (enumFieldNode == null) {
|
||||
return null;
|
||||
}
|
||||
InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode);
|
||||
InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode);
|
||||
if (sputInsn == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -333,17 +362,17 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
RegisterArg sgetResult = sgetInsn.getResult();
|
||||
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
|
||||
toRemove.add(sgetInsn);
|
||||
data.toRemove.add(sgetInsn);
|
||||
}
|
||||
toRemove.add(sputInsn);
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, co);
|
||||
data.toRemove.add(sputInsn);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, co);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) {
|
||||
InsnNode assignInsn = arg.getAssignInsn();
|
||||
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
|
||||
return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove);
|
||||
return processEnumFieldByField(data, assignInsn);
|
||||
}
|
||||
|
||||
SSAVar ssaVar = arg.getSVar();
|
||||
@@ -354,12 +383,12 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
|
||||
return null;
|
||||
}
|
||||
FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove);
|
||||
FieldNode enumFieldNode = searchEnumField(data, ssaVar);
|
||||
if (enumFieldNode == null) {
|
||||
enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum());
|
||||
cls.addField(enumFieldNode);
|
||||
enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum());
|
||||
data.cls.addField(enumFieldNode);
|
||||
}
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
||||
}
|
||||
|
||||
private FieldNode createFakeField(ClassNode cls, String name) {
|
||||
@@ -372,17 +401,17 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) {
|
||||
private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) {
|
||||
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
||||
return null;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
||||
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||
if (enumFieldNode == null) {
|
||||
return null;
|
||||
}
|
||||
toRemove.add(sputInsn);
|
||||
data.toRemove.add(sputInsn);
|
||||
return enumFieldNode;
|
||||
}
|
||||
|
||||
@@ -409,24 +438,32 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (ctrMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables");
|
||||
}
|
||||
return new EnumField(enumFieldNode, co);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) {
|
||||
for (InsnNode sputInsn : staticBlock.getInstructions()) {
|
||||
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode fieldNode = cls.searchField(f);
|
||||
if (Objects.equals(fieldNode, enumFieldNode)) {
|
||||
return sputInsn;
|
||||
private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) {
|
||||
for (BlockNode block : data.staticBlocks) {
|
||||
for (InsnNode sputInsn : block.getInstructions()) {
|
||||
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode fieldNode = data.cls.searchField(f);
|
||||
if (Objects.equals(fieldNode, enumFieldNode)) {
|
||||
return sputInsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
||||
private void removeEnumMethods(ClassNode cls, FieldNode valuesField) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||
MethodNode valuesMethod = null;
|
||||
// remove compiler generated methods
|
||||
@@ -605,4 +642,19 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class EnumData {
|
||||
final ClassNode cls;
|
||||
final MethodNode classInitMth;
|
||||
final List<BlockNode> staticBlocks;
|
||||
final List<InsnNode> toRemove = new ArrayList<>();
|
||||
FieldNode valuesField;
|
||||
InsnNode valuesInitInsn;
|
||||
|
||||
public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) {
|
||||
this.cls = cls;
|
||||
this.classInitMth = classInitMth;
|
||||
this.staticBlocks = staticBlocks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@@ -45,20 +46,22 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
moveStaticFieldsInit(cls);
|
||||
moveCommonFieldsInit(cls);
|
||||
if (!cls.getFields().isEmpty()) {
|
||||
moveStaticFieldsInit(cls);
|
||||
moveCommonFieldsInit(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class FieldInitInfo {
|
||||
final FieldNode fieldNode;
|
||||
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.putInsn = putInsn;
|
||||
this.singlePath = singlePath;
|
||||
this.canMove = canMove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,9 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
|| classInitMth.getBasicBlocks() == null) {
|
||||
return;
|
||||
}
|
||||
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isStatic)) {
|
||||
return;
|
||||
}
|
||||
while (processStaticFields(cls, classInitMth)) {
|
||||
// sometimes instructions moved to field init prevent from vars inline -> inline and try again
|
||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||
@@ -116,15 +122,15 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void moveCommonFieldsInit(ClassNode cls) {
|
||||
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isInstance)) {
|
||||
return;
|
||||
}
|
||||
List<MethodNode> constructors = getConstructorsList(cls);
|
||||
if (constructors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size());
|
||||
for (MethodNode constructorMth : constructors) {
|
||||
if (constructorMth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT);
|
||||
filterFieldsInit(inits);
|
||||
if (inits.isEmpty()) {
|
||||
@@ -168,19 +174,25 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
Set<BlockNode> singlePathBlocks = new HashSet<>();
|
||||
BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add);
|
||||
|
||||
boolean canReorder = true;
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
boolean fieldInsn = false;
|
||||
if (insn.getType() == putType) {
|
||||
IndexInsnNode putInsn = (IndexInsnNode) insn;
|
||||
FieldInfo field = (FieldInfo) putInsn.getIndex();
|
||||
if (field.getDeclClass().equals(cls.getClassInfo())) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null) {
|
||||
boolean singlePath = singlePathBlocks.contains(block);
|
||||
fieldsInit.add(new FieldInitInfo(fn, putInsn, singlePath));
|
||||
boolean canMove = canReorder && singlePathBlocks.contains(block);
|
||||
fieldsInit.add(new FieldInitInfo(fn, putInsn, canMove));
|
||||
fieldInsn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fieldInsn && canReorder && !insn.canReorder()) {
|
||||
canReorder = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldsInit;
|
||||
@@ -226,14 +238,14 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean checkInsn(FieldInitInfo initInfo) {
|
||||
if (!initInfo.singlePath) {
|
||||
if (!initInfo.canMove) {
|
||||
return false;
|
||||
}
|
||||
IndexInsnNode insn = initInfo.putInsn;
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (!wrapInsn.canReorderRecursive() && insn.contains(AType.EXC_CATCH)) {
|
||||
if (!wrapInsn.canReorder() && insn.contains(AType.EXC_CATCH)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -364,7 +376,7 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
AccessInfo accFlags = mth.getAccessFlags();
|
||||
if (!accFlags.isStatic() && accFlags.isConstructor()) {
|
||||
list.add(mth);
|
||||
if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
if (mth.isNoCode() || BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,10 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
// exclude self usage
|
||||
return null;
|
||||
}
|
||||
if (ctrUseCls.getTopParentClass().equals(cls)) {
|
||||
// exclude usage inside inner classes
|
||||
return null;
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth == ctr) {
|
||||
continue;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -45,7 +45,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
||||
}
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
||||
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
|
||||
return isSynthetic && canInlineMethod(mth, accessFlags);
|
||||
}
|
||||
|
||||
private static boolean canInlineMethod(MethodNode mth, AccessInfo accessFlags) {
|
||||
if (accessFlags.isStatic()) {
|
||||
return true;
|
||||
}
|
||||
return mth.isConstructor() && mth.root().getArgs().isInlineAnonymousClasses();
|
||||
}
|
||||
|
||||
private static void fixClassDependencies(MethodNode mth) {
|
||||
@@ -54,8 +61,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
||||
// remove possible cross dependency
|
||||
// to force class with inline method to be processed before its usage
|
||||
ClassNode useTopCls = useInMth.getTopParentClass();
|
||||
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
|
||||
useTopCls.addCodegenDep(parentClass);
|
||||
if (useTopCls != parentClass) {
|
||||
parentClass.removeDependency(useTopCls);
|
||||
useTopCls.addCodegenDep(parentClass);
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
parentClass.addDebugComment("Remove dependency: " + useTopCls + " to inline " + mth);
|
||||
useTopCls.addDebugComment("Add dependency: " + parentClass + " to inline " + mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +171,6 @@ public class BlockExceptionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected static void removeTmpConnections(MethodNode mth) {
|
||||
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
|
||||
}
|
||||
|
||||
private static void removeTmpConnection(BlockNode block) {
|
||||
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
||||
if (tmpEdgeAttr != null) {
|
||||
@@ -402,6 +398,13 @@ public class BlockExceptionHandler {
|
||||
}
|
||||
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
||||
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);
|
||||
}
|
||||
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.visitors.blocks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -432,7 +431,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
BlockNode loopHeader = loop.getStart();
|
||||
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||
if (preds.size() > 2) {
|
||||
List<BlockNode> blocks = new LinkedList<>(preds);
|
||||
List<BlockNode> blocks = new ArrayList<>(preds);
|
||||
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||
BlockNode first = blocks.remove(0);
|
||||
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||
|
||||
@@ -137,8 +137,9 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
}
|
||||
|
||||
static BlockNode startNewBlock(MethodNode mth, int offset) {
|
||||
BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset);
|
||||
mth.getBasicBlocks().add(block);
|
||||
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||
BlockNode block = new BlockNode(mth.getNextBlockCId(), blocks.size(), offset);
|
||||
blocks.add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
@@ -391,7 +392,8 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
&& block.getSuccessors().size() <= 1
|
||||
&& !block.getPredecessors().isEmpty()
|
||||
&& !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) {
|
||||
|
||||
@@ -25,23 +25,16 @@ public class DominatorTree {
|
||||
List<BlockNode> sorted = sortBlocks(mth);
|
||||
BlockNode[] doms = build(sorted);
|
||||
apply(sorted, doms);
|
||||
mth.setBasicBlocks(sorted);
|
||||
}
|
||||
|
||||
private static List<BlockNode> sortBlocks(MethodNode mth) {
|
||||
int blocksCount = mth.getBasicBlocks().size();
|
||||
BitSet reachSet = new BitSet(blocksCount);
|
||||
List<BlockNode> sorted = new ArrayList<>(blocksCount);
|
||||
BlockUtils.dfsVisit(mth, b -> {
|
||||
sorted.add(b);
|
||||
reachSet.set(b.getId());
|
||||
});
|
||||
if (reachSet.cardinality() != blocksCount) {
|
||||
BlockUtils.dfsVisit(mth, sorted::add);
|
||||
if (sorted.size() != blocksCount) {
|
||||
throw new JadxRuntimeException("Found unreachable blocks");
|
||||
}
|
||||
for (int i = 0; i < blocksCount; i++) {
|
||||
sorted.get(i).setId(i);
|
||||
}
|
||||
mth.setBasicBlocks(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -19,6 +20,10 @@ public class FinallyExtractInfo {
|
||||
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
||||
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) {
|
||||
this.mth = mth;
|
||||
this.finallyHandler = finallyHandler;
|
||||
@@ -54,6 +59,27 @@ public class FinallyExtractInfo {
|
||||
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
|
||||
public String toString() {
|
||||
return "FinallyExtractInfo{"
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
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.ssa.SSATransform;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.ListUtils;
|
||||
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.
|
||||
*/
|
||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||
extractInfo.setCurDupSlice(null);
|
||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||
int dupSize = dupInsns.size();
|
||||
@@ -386,7 +389,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
int startPos;
|
||||
int endPos = 0;
|
||||
if (dupSize == finSize) {
|
||||
if (!checkInsns(dupInsns, finallyInsns, 0)) {
|
||||
if (!checkInsns(extractInfo, dupInsns, finallyInsns, 0)) {
|
||||
return null;
|
||||
}
|
||||
startPos = 0;
|
||||
@@ -394,11 +397,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
// dupSize > finSize
|
||||
startPos = dupSize - finSize;
|
||||
// fast check from end of block
|
||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||
if (!checkInsns(extractInfo, dupInsns, finallyInsns, startPos)) {
|
||||
// search start insn
|
||||
boolean found = false;
|
||||
for (int i = 1; i < startPos; i++) {
|
||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
||||
if (checkInsns(extractInfo, dupInsns, finallyInsns, i)) {
|
||||
startPos = i;
|
||||
endPos = finSize + i;
|
||||
found = true;
|
||||
@@ -414,6 +417,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
// put instructions into slices
|
||||
boolean complete;
|
||||
InsnsSlice slice = new InsnsSlice();
|
||||
extractInfo.setCurDupSlice(slice);
|
||||
int endIndex;
|
||||
if (endPos != 0) {
|
||||
endIndex = endPos + 1;
|
||||
@@ -453,11 +457,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
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--) {
|
||||
InsnNode startInsn = finallyInsns.get(i);
|
||||
InsnNode remInsn = remInsns.get(delta + i);
|
||||
if (!sameInsns(remInsn, startInsn)) {
|
||||
InsnNode dupInsn = dupInsns.get(delta + i);
|
||||
if (!sameInsns(extractInfo, dupInsn, startInsn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -509,8 +514,9 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
if (dupInsnCount < finallyInsnCount) {
|
||||
return false;
|
||||
}
|
||||
extractInfo.setCurDupInsns(dupInsns, 0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -524,26 +530,85 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn) {
|
||||
if (!remInsn.isSame(fInsn)) {
|
||||
private static boolean sameInsns(FinallyExtractInfo extractInfo, InsnNode dupInsn, InsnNode fInsn) {
|
||||
if (!dupInsn.isSame(fInsn)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: check instance arg in ConstructorInsn
|
||||
// TODO: compare literals
|
||||
for (int i = 0; i < remInsn.getArgsCount(); i++) {
|
||||
InsnArg remArg = remInsn.getArg(i);
|
||||
for (int i = 0; i < dupInsn.getArgsCount(); i++) {
|
||||
InsnArg dupArg = dupInsn.getArg(i);
|
||||
InsnArg fArg = fInsn.getArg(i);
|
||||
if (remArg.isRegister() != fArg.isRegister()) {
|
||||
if (!isSameArgs(extractInfo, dupArg, fArg)) {
|
||||
return false;
|
||||
}
|
||||
boolean remConst = remArg.isConst();
|
||||
if (remConst != fArg.isConst()) {
|
||||
return false;
|
||||
}
|
||||
if (remConst && !remArg.isSameConst(fArg)) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
|
||||
public class CleanRegions extends AbstractVisitor {
|
||||
@@ -42,6 +43,13 @@ public class CleanRegions extends AbstractVisitor {
|
||||
BlockNode block = (BlockNode) container;
|
||||
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) {
|
||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||
for (IContainer subBlock : subBlocks) {
|
||||
|
||||
@@ -19,6 +19,10 @@ public class DepthRegionTraversal {
|
||||
traverseInternal(mth, visitor, mth.getRegion());
|
||||
}
|
||||
|
||||
public static void traverse(MethodNode mth, IContainer container, IRegionVisitor visitor) {
|
||||
traverseInternal(mth, visitor, container);
|
||||
}
|
||||
|
||||
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
|
||||
boolean repeat;
|
||||
int k = 0;
|
||||
|
||||
@@ -3,8 +3,10 @@ package jadx.core.dex.visitors.regions;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
@@ -45,7 +47,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||
@SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
|
||||
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||
return;
|
||||
@@ -79,9 +81,15 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
|
||||
boolean lastRegion = RegionUtils.hasExitEdge(ifRegion);
|
||||
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
||||
// single return at method end will be removed later
|
||||
InsnNode lastElseInsn = RegionUtils.getLastInsn(ifRegion.getElseRegion());
|
||||
if (lastElseInsn != null && lastElseInsn.getType() == InsnType.THROW) {
|
||||
// move `throw` into `then` block
|
||||
invertIfRegion(ifRegion);
|
||||
} else {
|
||||
// single return at method end will be removed later
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!lastRegion) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
return false;
|
||||
}
|
||||
// can't make loop if argument from increment instruction is assign in loop
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
List<RegisterArg> args = new ArrayList<>();
|
||||
incrInsn.getRegisterArgs(args);
|
||||
for (RegisterArg iArg : args) {
|
||||
try {
|
||||
@@ -268,7 +268,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
|
||||
return false;
|
||||
}
|
||||
List<InsnNode> toSkip = new LinkedList<>();
|
||||
List<InsnNode> toSkip = new ArrayList<>();
|
||||
RegisterArg iterVar;
|
||||
if (nextCall.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
|
||||
|
||||
@@ -464,7 +464,7 @@ public class RegionMaker {
|
||||
BlockNode exitEnd = BlockUtils.followEmptyPath(exit);
|
||||
List<LoopInfo> loops = exitEnd.getAll(AType.LOOP);
|
||||
for (LoopInfo loopAtEnd : loops) {
|
||||
if (loopAtEnd != loop) {
|
||||
if (loopAtEnd != loop && loop.hasParent(loopAtEnd)) {
|
||||
insertEdge = exitEdge;
|
||||
confirm = true;
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
@@ -73,11 +74,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
if (elseRegion == null) {
|
||||
if (mth.isConstructor()) {
|
||||
// force ternary conversion to inline all code in 'super' or 'this' calls
|
||||
return processOneBranchTernary(mth, ifRegion);
|
||||
}
|
||||
return false;
|
||||
return processOneBranchTernary(mth, ifRegion);
|
||||
}
|
||||
BlockNode tb = getTernaryInsnBlock(thenRegion);
|
||||
BlockNode eb = getTernaryInsnBlock(elseRegion);
|
||||
@@ -93,21 +90,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// no debug info
|
||||
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
|
||||
// don't make nested ternary by default
|
||||
// TODO: add addition checks
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!verifyLineHints(mth, thenInsn, elseInsn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterArg thenResArg = thenInsn.getResult();
|
||||
@@ -184,6 +168,20 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean verifyLineHints(MethodNode mth, InsnNode thenInsn, InsnNode elseInsn) {
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
return checkLineStats(thenInsn, elseInsn);
|
||||
}
|
||||
// don't make nested ternary by default
|
||||
// TODO: add addition checks
|
||||
return !containsTernary(thenInsn) && !containsTernary(elseInsn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
|
||||
for (BlockNode block : conditionBlocks) {
|
||||
if (block != header) {
|
||||
@@ -277,6 +275,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg.getSVar().getUseList().size() != 1) {
|
||||
@@ -296,17 +295,45 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
if (otherArg == null) {
|
||||
return;
|
||||
}
|
||||
InsnNode elseAssign = otherArg.getAssignInsn();
|
||||
if (mth.isConstructor() || (mth.getParentClass().isEnum() && mth.getMethodInfo().isClassInit())) {
|
||||
// forcing ternary inline for constructors (will help in moving super call to the top) and enums
|
||||
// skip code style checks
|
||||
} else {
|
||||
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||
if (!verifyLineHints(mth, insn, elseAssign)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (insn.getResult().sameCodeVar(otherArg)) {
|
||||
// don't use same variable in else branch to prevent: l = (l == 0) ? 1 : l
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
BlockNode header = ifRegion.getConditionBlocks().get(0);
|
||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||
return;
|
||||
}
|
||||
InsnArg elseArg;
|
||||
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||
// inline constant
|
||||
SSAVar elseVar = elseAssign.getResult().getSVar();
|
||||
if (elseVar.getUseCount() == 1 && elseVar.getOnlyOneUseInPhi() == phiInsn) {
|
||||
InsnRemover.remove(mth, elseAssign);
|
||||
}
|
||||
elseArg = InsnArg.wrapInsnIntoArg(elseAssign);
|
||||
} else {
|
||||
elseArg = otherArg;
|
||||
}
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
||||
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg);
|
||||
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), elseArg);
|
||||
ternInsn.simplifyCondition();
|
||||
|
||||
InsnRemover.unbindResult(mth, insn);
|
||||
InsnList.remove(block, insn);
|
||||
|
||||
InsnRemover.unbindAllArgs(mth, phiInsn);
|
||||
header.getInstructions().clear();
|
||||
ternInsn.rebindArgs();
|
||||
|
||||
@@ -14,6 +14,9 @@ import jadx.core.Consts;
|
||||
import jadx.core.codegen.json.JsonMappingGen;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -195,7 +198,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
Set<String> names = new HashSet<>(methods.size());
|
||||
for (MethodNode mth : methods) {
|
||||
String signature = mth.getMethodInfo().makeSignature(true, false);
|
||||
if (!names.add(signature)) {
|
||||
if (!names.add(signature) && canRename(mth)) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
|
||||
}
|
||||
@@ -203,6 +206,23 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canRename(MethodNode mth) {
|
||||
if (mth.contains(AFlag.DONT_RENAME)) {
|
||||
return false;
|
||||
}
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||
if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) {
|
||||
// ignore rename if exists related method from same class (bridge method in most cases)
|
||||
// such rename will also rename current method and will not help to resolve name collision
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
|
||||
Set<String> rootPkgs = collectRootPkgs(classes);
|
||||
root.getCacheStorage().setRootPkgs(rootPkgs);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.dex.visitors.shrink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -30,7 +30,7 @@ final class ArgsInfo {
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
List<RegisterArg> args = new ArrayList<>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.core.dex.visitors.ssa;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -81,7 +81,7 @@ public class SSATransform extends AbstractVisitor {
|
||||
int blocksCount = blocks.size();
|
||||
BitSet hasPhi = new BitSet(blocksCount);
|
||||
BitSet processed = new BitSet(blocksCount);
|
||||
Deque<BlockNode> workList = new LinkedList<>();
|
||||
Deque<BlockNode> workList = new ArrayDeque<>();
|
||||
|
||||
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
||||
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
||||
@@ -136,7 +136,7 @@ public class SSATransform extends AbstractVisitor {
|
||||
RenameState initState = RenameState.init(mth);
|
||||
initPhiInEnterBlock(initState);
|
||||
|
||||
Deque<RenameState> stack = new LinkedList<>();
|
||||
Deque<RenameState> stack = new ArrayDeque<>();
|
||||
stack.push(initState);
|
||||
while (!stack.isEmpty()) {
|
||||
RenameState state = stack.pop();
|
||||
|
||||
+4
-9
@@ -913,25 +913,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
boolean fixed = false;
|
||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
||||
if (bound.getBound() == BoundEnum.USE
|
||||
&& fixBooleanUsage(mth, bound)) {
|
||||
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
|
||||
if (fixBooleanUsage(mth, arg)) {
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
private boolean fixBooleanUsage(MethodNode mth, ITypeBound bound) {
|
||||
ArgType boundType = bound.getType();
|
||||
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
|
||||
ArgType boundType = boundArg.getInitType();
|
||||
if (boundType == ArgType.BOOLEAN
|
||||
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg boundArg = bound.getArg();
|
||||
if (boundArg == null) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = boundArg.getParentInsn();
|
||||
if (insn == null || insn.getType() == InsnType.IF) {
|
||||
return false;
|
||||
|
||||
@@ -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
|
||||
public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) {
|
||||
|
||||
@@ -29,8 +29,12 @@ public final class InsnList implements Iterable<InsnNode> {
|
||||
}
|
||||
|
||||
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();
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int i = startOffset; i < size; i++) {
|
||||
if (list.get(i) == insn) {
|
||||
return i;
|
||||
}
|
||||
@@ -38,6 +42,14 @@ public final class InsnList implements Iterable<InsnNode> {
|
||||
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) {
|
||||
return getIndex(list, insn);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -228,6 +229,18 @@ public class InsnRemover {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
|
||||
public static void removeAllAndUnbind(MethodNode mth, IContainer container, List<InsnNode> insns) {
|
||||
unbindInsns(mth, insns);
|
||||
RegionUtils.visitBlocks(mth, container, b -> removeAll(b.getInstructions(), insns));
|
||||
}
|
||||
|
||||
public static void removeAllAndUnbind(MethodNode mth, List<InsnNode> insns) {
|
||||
unbindInsns(mth, insns);
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
|
||||
@@ -160,6 +160,10 @@ public class ListUtils {
|
||||
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) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,9 +23,12 @@ import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
||||
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegionUtils {
|
||||
@@ -145,20 +149,6 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IContainer getLastRegion(@Nullable IContainer container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
if (container instanceof IBlock || container instanceof IBranchRegion) {
|
||||
return container;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static boolean isExitBlock(MethodNode mth, IContainer container) {
|
||||
if (container instanceof BlockNode) {
|
||||
return BlockUtils.isExitBlock(mth, (BlockNode) container);
|
||||
@@ -289,7 +279,11 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (container instanceof IRegion) {
|
||||
}
|
||||
if (container instanceof LoopRegion) {
|
||||
return true;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
for (IContainer block : region.getSubBlocks()) {
|
||||
if (notEmpty(block)) {
|
||||
@@ -297,9 +291,8 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) {
|
||||
@@ -483,4 +476,13 @@ public class RegionUtils {
|
||||
}
|
||||
return "Unknown container type: " + container.getClass();
|
||||
}
|
||||
|
||||
public static void visitBlocks(MethodNode mth, IContainer container, Consumer<IBlock> visitor) {
|
||||
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void processBlock(MethodNode mth, IBlock block) {
|
||||
visitor.accept(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public class AndroidResourcesUtils {
|
||||
FieldNode newResField = new FieldNode(typeCls, rFieldInfo,
|
||||
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
|
||||
newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
|
||||
typeCls.getFields().add(newResField);
|
||||
typeCls.addField(newResField);
|
||||
if (rClsExists) {
|
||||
newResField.addInfoComment("Added by JADX");
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
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.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.JarEntry;
|
||||
@@ -108,13 +112,8 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
File[] content = dir.listFiles();
|
||||
if (content != null) {
|
||||
for (File file : content) {
|
||||
deleteDir(file);
|
||||
}
|
||||
}
|
||||
return dir.delete();
|
||||
deleteDir(dir.toPath());
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
pathStream.sorted(Comparator.reverseOrder())
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, FILE_DELETE_VISITOR);
|
||||
} catch (Exception 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 {
|
||||
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 {
|
||||
|
||||
@@ -4,10 +4,8 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
|
||||
@@ -175,7 +173,7 @@ public class ManifestAttributes {
|
||||
if (attr.getType() == MAttrType.ENUM) {
|
||||
return attr.getValues().get(value);
|
||||
} else if (attr.getType() == MAttrType.FLAG) {
|
||||
List<String> flagList = new LinkedList<>();
|
||||
List<String> flagList = new ArrayList<>();
|
||||
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
|
||||
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
|
||||
for (Long key : attrKeys) {
|
||||
@@ -188,7 +186,7 @@ public class ManifestAttributes {
|
||||
value ^= key;
|
||||
}
|
||||
}
|
||||
return flagList.stream().collect(Collectors.joining("|"));
|
||||
return String.join("|", flagList);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,12 @@ public class ParserConstants {
|
||||
protected static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
|
||||
protected static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
|
||||
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||
protected static final int RES_TABLE_TYPE_TYPE = 0x0201;
|
||||
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200; // 512
|
||||
protected static final int RES_TABLE_TYPE_TYPE = 0x0201; // 513
|
||||
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; // 514
|
||||
protected static final int RES_TABLE_TYPE_LIBRARY = 0x0203; // 515
|
||||
protected static final int RES_TABLE_TYPE_OVERLAY = 0x0204; // 516
|
||||
protected static final int RES_TABLE_TYPE_STAGED_ALIAS = 0x0206; // 517
|
||||
|
||||
/**
|
||||
* Type constants
|
||||
|
||||
@@ -10,14 +10,18 @@ import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
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.RawNamedValue;
|
||||
import jadx.core.xmlgen.entry.RawValue;
|
||||
@@ -149,13 +153,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
while (is.getPos() < endPos) {
|
||||
long chunkStart = is.getPos();
|
||||
int type = is.readInt16();
|
||||
if (type == RES_NULL_TYPE) {
|
||||
continue;
|
||||
}
|
||||
if (type == RES_TABLE_TYPE_SPEC_TYPE) {
|
||||
parseTypeSpecChunk();
|
||||
} else if (type == RES_TABLE_TYPE_TYPE) {
|
||||
parseTypeChunk(chunkStart, pkg);
|
||||
LOG.trace("res package chunk start at {} type {}", chunkStart, type);
|
||||
switch (type) {
|
||||
case RES_NULL_TYPE:
|
||||
LOG.info("Null chunk type encountered at offset {}", chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_TYPE: // 0x0201
|
||||
parseTypeChunk(chunkStart, pkg);
|
||||
break;
|
||||
case RES_TABLE_TYPE_SPEC_TYPE: // 0x0202
|
||||
parseTypeSpecChunk(chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_LIBRARY: // 0x0203
|
||||
parseLibraryTypeChunk(chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_OVERLAY: // 0x0204
|
||||
throw new IOException(
|
||||
String.format("Encountered unsupported chunk type TYPE_OVERLAY at offset 0x%x ", chunkStart));
|
||||
case RES_TABLE_TYPE_STAGED_ALIAS: // 0x0206
|
||||
throw new IOException(
|
||||
String.format("Encountered unsupported chunk type TYPE_STAGED_ALIAS at offset 0x%x ", chunkStart));
|
||||
default:
|
||||
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
|
||||
}
|
||||
}
|
||||
return pkg;
|
||||
@@ -188,10 +207,10 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void parseTypeSpecChunk() throws IOException {
|
||||
private void parseTypeSpecChunk(long chunkStart) throws IOException {
|
||||
is.checkInt16(0x0010, "Unexpected type spec header size");
|
||||
/* int size = */
|
||||
is.readInt32();
|
||||
int chunkSize = is.readInt32();
|
||||
long expectedEndPos = chunkStart + chunkSize;
|
||||
|
||||
int id = is.readInt8();
|
||||
is.skip(3);
|
||||
@@ -199,6 +218,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
int entryFlag = is.readInt32();
|
||||
}
|
||||
if (is.getPos() != expectedEndPos) {
|
||||
throw new IOException(String.format("Error reading type spec chunk at offset 0x%x", chunkStart));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseLibraryTypeChunk(long chunkStart) throws IOException {
|
||||
LOG.trace("parsing library type chunk starting at offset {}", chunkStart);
|
||||
is.checkInt16(12, "Unexpected header size");
|
||||
int chunkSize = is.readInt32();
|
||||
long expectedEndPos = chunkStart + chunkSize;
|
||||
int count = is.readInt32();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int packageId = is.readInt32();
|
||||
String packageName = is.readString16Fixed(128);
|
||||
LOG.info("Found resource shared library {}, pkgId: {}", packageName, packageId);
|
||||
if (is.getPos() > expectedEndPos) {
|
||||
throw new IOException("reading after chunk end");
|
||||
}
|
||||
}
|
||||
if (is.getPos() != expectedEndPos) {
|
||||
throw new IOException(String.format("Error reading library chunk at offset 0x%x", chunkStart));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
|
||||
@@ -237,6 +278,13 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
parseEntry(pkg, id, i, config.getQualifiers());
|
||||
}
|
||||
}
|
||||
if (chunkEnd > is.getPos()) {
|
||||
// Skip remaining unknown data in this chunk (e.g. type 8 entries")
|
||||
long skipSize = chunkEnd - is.getPos();
|
||||
LOG.debug("Unknown data at the end of type chunk encountered, skipping {} bytes and continuing at offset {}", skipSize,
|
||||
chunkEnd);
|
||||
is.skip(skipSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseEntry(PackageChunk pkg, int typeId, int entryId, String config) throws IOException {
|
||||
@@ -287,35 +335,66 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
if (renamedKey != null) {
|
||||
return renamedKey;
|
||||
}
|
||||
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
||||
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
|
||||
// styles might contain dots in name, search for alias only for resources names
|
||||
if (typeName.equals("style")) {
|
||||
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
|
||||
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();
|
||||
boolean first = true;
|
||||
while (m.find()) {
|
||||
while (matcher.find()) {
|
||||
if (!first) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(m.group());
|
||||
sb.append(matcher.group());
|
||||
first = false;
|
||||
}
|
||||
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
|
||||
String newResName = String.format("res_0x%08x", resRef);
|
||||
String cleanedResName = sb.toString();
|
||||
if (!cleanedResName.isEmpty()) {
|
||||
newResName += "_" + cleanedResName.toLowerCase();
|
||||
}
|
||||
return newResName;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private RawNamedValue parseValueMap() throws IOException {
|
||||
|
||||
@@ -168,11 +168,13 @@ public class ResXmlGen {
|
||||
private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
||||
String nameStr = vp.decodeNameRef(value.getNameRef());
|
||||
String valueStr = vp.decodeValue(value.getRawValue());
|
||||
int dataType = value.getRawValue().getDataType();
|
||||
|
||||
if (!typeName.equals("attr")) {
|
||||
if (valueStr == null || valueStr.equals("0")) {
|
||||
if (dataType == ParserConstants.TYPE_REFERENCE && (valueStr == null || valueStr.equals("0"))) {
|
||||
valueStr = "@null";
|
||||
}
|
||||
if (nameStr != null) {
|
||||
if (dataType == ParserConstants.TYPE_INT_DEC && nameStr != null) {
|
||||
try {
|
||||
int intVal = Integer.parseInt(valueStr);
|
||||
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class ResourcesSaver implements Runnable {
|
||||
private void save(ResContainer rc, File outDir) {
|
||||
File outFile = new File(outDir, rc.getFileName());
|
||||
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
|
||||
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
|
||||
LOG.error("Invalid resource name or path traversal attack detected: {}", outFile.getPath());
|
||||
return;
|
||||
}
|
||||
saveToFile(rc, outFile);
|
||||
|
||||
@@ -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() {
|
||||
args.setDeobfuscationOn(true);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||
args.setDeobfuscationMinLength(2);
|
||||
args.setDeobfuscationMaxLength(64);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class TestCompiler implements Closeable {
|
||||
assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
||||
return mth.invoke(inst, args);
|
||||
} catch (Throwable e) {
|
||||
IntegrationTest.rethrow("Invoke error", e);
|
||||
IntegrationTest.rethrow("Invoke error for method: " + methodName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
@SuppressWarnings("CommentedOutCode")
|
||||
public class TestBooleanToInt extends SmaliTest {
|
||||
|
||||
// @formatter:off
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
@SuppressWarnings("CommentedOutCode")
|
||||
public class TestBooleanToInt2 extends SmaliTest {
|
||||
|
||||
// @formatter:off
|
||||
/*
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
boolean v = getValue();
|
||||
use1(Integer.valueOf(v));
|
||||
use2(v);
|
||||
}
|
||||
|
||||
private boolean getValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void use1(Integer v) {
|
||||
}
|
||||
|
||||
private void use2(int v) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("use1(Integer.valueOf(value ? 1 : 0));")
|
||||
.containsOne("use2(value ? 1 : 0);");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestElseIfCodeStyle extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class TestCls {
|
||||
|
||||
public void test(String str) {
|
||||
if ("a".equals(str)) {
|
||||
call(1);
|
||||
} else if ("b".equals(str)) {
|
||||
call(2);
|
||||
} else if ("c".equals(str)) {
|
||||
call(3);
|
||||
}
|
||||
}
|
||||
|
||||
private void call(int i) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.doesNotContain("!\"c\".equals(str)")
|
||||
.doesNotContain("{" + ICodeWriter.NL + indent(2) + "} else {"); // no empty `then` block
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
public class TestSwitchTryBreak extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void test(int x) {
|
||||
switch (x) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
String res;
|
||||
if ("android".equals(toString())) {
|
||||
res = "hello";
|
||||
} else {
|
||||
try {
|
||||
if (String.CASE_INSENSITIVE_ORDER != null) {
|
||||
break;
|
||||
}
|
||||
res = "hi";
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
System.out.println("returning");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@NotYetImplemented
|
||||
public void test() {
|
||||
getClassNode(TestCls.class);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
public class TestSwitchTryBreak2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void test(int x) {
|
||||
switch (x) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
String res;
|
||||
if ("android".equals(toString())) {
|
||||
res = "hello";
|
||||
} else {
|
||||
try {
|
||||
if (x == 5) {
|
||||
break;
|
||||
}
|
||||
res = "hi";
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
System.out.println("returning");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@NotYetImplemented
|
||||
public void test() {
|
||||
getClassNode(TestCls.class);
|
||||
}
|
||||
}
|
||||
@@ -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();");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnumsWithCustomInit extends IntegrationTest {
|
||||
|
||||
public enum TestCls {
|
||||
ONE("I"),
|
||||
TWO("II"),
|
||||
THREE("III");
|
||||
|
||||
public static final Map<String, TestCls> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (TestCls value : values()) {
|
||||
MAP.put(value.toString(), value);
|
||||
}
|
||||
}
|
||||
|
||||
private final String str;
|
||||
|
||||
TestCls(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("ONE(\"I\"),")
|
||||
.doesNotContain("new TestEnumsWithCustomInit$TestCls(");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnumsWithTernary extends IntegrationTest {
|
||||
|
||||
public enum TestCls {
|
||||
FIRST(useNumber() ? "1" : "A"),
|
||||
SECOND(useNumber() ? "2" : "B"),
|
||||
ANY(useNumber() ? "1" : "2");
|
||||
|
||||
private final String str;
|
||||
|
||||
TestCls(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static boolean useNumber() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.D8_J8 })
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("ANY(useNumber() ? \"1\" : \"2\");")
|
||||
.doesNotContain("static {");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.tests.integration.inline;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestSyntheticBridgeRename extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public static class TestCls {
|
||||
private abstract class Inner<V> {
|
||||
public abstract V get(String value);
|
||||
}
|
||||
|
||||
public class IntInner extends Inner<Integer> {
|
||||
public Integer get(String value) {
|
||||
return value.length();
|
||||
}
|
||||
}
|
||||
|
||||
public void test() {
|
||||
IntInner inner = new IntInner();
|
||||
call(inner.get("a"));
|
||||
}
|
||||
|
||||
private static void call(Integer value) {
|
||||
}
|
||||
}
|
||||
|
||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(searchCls(cls.getInnerClasses(), "IntInner").getMethods())
|
||||
.as("check that bridge method was generated by compiler")
|
||||
.haveAtLeastOne(new Condition<>(mth -> mth.getAccessFlags().isBridge(), "bridge"));
|
||||
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.doesNotContain("mo0get")
|
||||
.containsOne("call(inner.get(\"a\"));");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package jadx.tests.integration.names;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -26,7 +26,7 @@ public class TestNameAssign2 extends IntegrationTest {
|
||||
int blocksCount = blocks.size();
|
||||
BitSet hasPhi = new BitSet(blocksCount);
|
||||
BitSet processed = new BitSet(blocksCount);
|
||||
Deque<BlockNode> workList = new LinkedList<>();
|
||||
Deque<BlockNode> workList = new ArrayDeque<>();
|
||||
|
||||
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
||||
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
||||
|
||||
@@ -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();");
|
||||
}
|
||||
}
|
||||
@@ -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 static jadx.tests.api.utils.JadxMatchers.containsLines;
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class TestTryCatchFinally6 extends IntegrationTest {
|
||||
@@ -54,15 +55,7 @@ public class TestTryCatchFinally6 extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsLines(2,
|
||||
"FileInputStream fileInputStream = null;",
|
||||
"try {",
|
||||
indent() + "call();",
|
||||
indent() + "fileInputStream = new FileInputStream(\"1.txt\");",
|
||||
"} finally {",
|
||||
indent() + "if (fileInputStream != null) {",
|
||||
indent() + indent() + "fileInputStream.close();",
|
||||
indent() + '}',
|
||||
"}"));
|
||||
// impossible to proof that variables should be merged, so can't restore finally block here
|
||||
assertThat(code, containsOne("if (0 != 0) {"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.class public Lconditions/TestBooleanToInt2;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public test()V
|
||||
.registers 3
|
||||
invoke-direct {p0}, Lconditions/TestBooleanToInt2;->getValue()Z
|
||||
move-result v0
|
||||
invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
|
||||
move-result-object v1
|
||||
invoke-direct {p0, v1}, Lconditions/TestBooleanToInt2;->use1(Ljava/lang/Integer;)V
|
||||
invoke-direct {p0, v0}, Lconditions/TestBooleanToInt2;->use2(I)V
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private getValue()Z
|
||||
.registers 2
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
.end method
|
||||
|
||||
.method private use1(Ljava/lang/Integer;)V
|
||||
.registers 2
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private use2(I)V
|
||||
.registers 2
|
||||
return-void
|
||||
.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
|
||||
+18
-12
@@ -9,25 +9,25 @@ dependencies {
|
||||
implementation(project(':jadx-core'))
|
||||
implementation(project(":jadx-cli"))
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
implementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
|
||||
implementation 'com.fifesoft:rsyntaxtextarea:3.2.0'
|
||||
implementation 'com.fifesoft:rsyntaxtextarea:3.3.0'
|
||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
|
||||
implementation 'com.formdev:flatlaf:2.4'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.4'
|
||||
implementation 'com.formdev:flatlaf-extras:2.4'
|
||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||
implementation 'com.formdev:flatlaf:2.6'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.6'
|
||||
implementation 'com.formdev:flatlaf-extras:2.6'
|
||||
implementation 'com.formdev:svgSalamander:1.1.4'
|
||||
|
||||
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-text:1.9'
|
||||
implementation 'org.apache.commons:commons-text:1.10.0'
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
|
||||
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
||||
implementation 'com.android.tools.build:apksig:7.2.1'
|
||||
implementation 'io.github.hqktech:jdwp:1.0'
|
||||
implementation 'com.android.tools.build:apksig:7.3.1'
|
||||
implementation 'io.github.skylot:jdwp:2.0.0'
|
||||
|
||||
// TODO: Switch back to upstream once this PR gets merged:
|
||||
// https://github.com/FabricMC/mapping-io/pull/19
|
||||
@@ -42,7 +42,8 @@ application {
|
||||
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
|
||||
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 {
|
||||
@@ -89,7 +90,7 @@ launch4j {
|
||||
windowTitle = 'jadx'
|
||||
companyName = 'jadx'
|
||||
jreMinVersion = '11'
|
||||
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC']
|
||||
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC', '-Djava.util.Arrays.useLegacyMergeSort=true']
|
||||
jreRuntimeBits = "64"
|
||||
bundledJre64Bit = true
|
||||
initialHeapPercent = 5
|
||||
@@ -138,3 +139,8 @@ task distWinWithJre(type: Zip, dependsOn: ['copyDistWinWithJre']) {
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task addNewNLSLines(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass.set('jadx.gui.utils.tools.NLSAddNewLines')
|
||||
}
|
||||
|
||||
@@ -44,11 +44,12 @@ import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
||||
private static final String ONCREATE_SIGNATURE = "onCreate(Landroid/os/Bundle;)V";
|
||||
private static final Map<String, RuntimeType> TYPE_MAP = new HashMap<>();
|
||||
private static final RuntimeType[] POSSIBLE_TYPES = { RuntimeType.OBJECT, RuntimeType.INT, RuntimeType.LONG };
|
||||
private static final int DEFAULT_CACHE_SIZE = 512;
|
||||
|
||||
private JDebuggerPanel debuggerPanel;
|
||||
private SmaliDebugger debugger;
|
||||
@@ -1115,7 +1116,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
private long thisID;
|
||||
|
||||
public FrameNode(long threadID, SmaliDebugger.Frame frame) {
|
||||
cache = new StringBuilder(16);
|
||||
cache = new StringBuilder(DEFAULT_CACHE_SIZE);
|
||||
this.frame = frame;
|
||||
this.threadID = threadID;
|
||||
regNodes = Collections.emptyList();
|
||||
@@ -1153,7 +1154,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
public void setSignatures(String clsSig, String mthSig) {
|
||||
this.clsSig = clsSig;
|
||||
this.mthSig = mthSig;
|
||||
this.cache.delete(0, this.cache.length());
|
||||
resetCache();
|
||||
}
|
||||
|
||||
public String getClsSig() {
|
||||
@@ -1167,7 +1168,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
public void updateCodeOffset(long codeOffset) {
|
||||
this.codeOffset = codeOffset;
|
||||
if (this.codeOffset > -1) {
|
||||
this.cache.delete(0, this.cache.length());
|
||||
resetCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,27 +1210,34 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCache() {
|
||||
// Do not reuse thee existing cache instance as this can result in
|
||||
// multi-threading access issues in case toString() method is active
|
||||
this.cache = new StringBuilder(DEFAULT_CACHE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (cache.length() == 0) {
|
||||
StringBuilder sbCache = cache;
|
||||
if (sbCache.length() == 0) {
|
||||
long off = getCodeOffset();
|
||||
if (off < 0) {
|
||||
cache.append(String.format("index: %-4d ", off));
|
||||
sbCache.append(String.format("index: %-4d ", off));
|
||||
} else {
|
||||
cache.append(String.format("index: %04x ", off));
|
||||
sbCache.append(String.format("index: %04x ", off));
|
||||
}
|
||||
if (clsSig == null) {
|
||||
cache.append("clsID: ").append(frame.getClassID());
|
||||
sbCache.append("clsID: ").append(frame.getClassID());
|
||||
} else {
|
||||
cache.append(clsSig).append("->");
|
||||
sbCache.append(clsSig).append("->");
|
||||
}
|
||||
if (mthSig == null) {
|
||||
cache.append(" mthID: ").append(frame.getMethodID());
|
||||
sbCache.append(" mthID: ").append(frame.getMethodID());
|
||||
} else {
|
||||
cache.append(mthSig);
|
||||
sbCache.append(mthSig);
|
||||
}
|
||||
}
|
||||
return cache.toString();
|
||||
return sbCache.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
|
||||
|
||||
abstract class EventListenerAdapter {
|
||||
void onVMStart(VMStartEvent event) {
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.device.protocol.ADBDevice;
|
||||
import jadx.gui.ui.panel.LogcatPanel;
|
||||
|
||||
public class LogcatController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);
|
||||
|
||||
private final ADBDevice adbDevice;
|
||||
private final LogcatPanel logcatPanel;
|
||||
private Timer timer;
|
||||
private final String timezone;
|
||||
private LogcatInfo recent = null;
|
||||
private ArrayList<LogcatInfo> events = new ArrayList<>();
|
||||
private LogcatFilter filter = new LogcatFilter(null, null);
|
||||
private String status = "null";
|
||||
|
||||
public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
|
||||
this.adbDevice = adbDevice;
|
||||
this.logcatPanel = logcatPanel;
|
||||
this.timezone = adbDevice.getTimezone();
|
||||
this.startLogcat();
|
||||
}
|
||||
|
||||
public void startLogcat() {
|
||||
timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
getLog();
|
||||
}
|
||||
}, 0, 1000);
|
||||
this.status = "running";
|
||||
}
|
||||
|
||||
public void stopLogcat() {
|
||||
timer.cancel();
|
||||
this.status = "stopped";
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public void clearLogcat() {
|
||||
try {
|
||||
adbDevice.clearLogcat();
|
||||
clearEvents();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clear Logcat", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void getLog() {
|
||||
if (!logcatPanel.isReady()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
byte[] buf;
|
||||
if (recent == null) {
|
||||
buf = adbDevice.getBinaryLogcat();
|
||||
} else {
|
||||
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
|
||||
}
|
||||
if (buf == null) {
|
||||
return;
|
||||
}
|
||||
ByteBuffer in = ByteBuffer.wrap(buf);
|
||||
in.order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (in.remaining() > 20) {
|
||||
|
||||
LogcatInfo eInfo = null;
|
||||
byte[] msgBuf;
|
||||
short eLen = in.getShort();
|
||||
short eHdrLen = in.getShort();
|
||||
if (eLen + eHdrLen > in.remaining()) {
|
||||
return;
|
||||
}
|
||||
switch (eHdrLen) {
|
||||
case 20: // header length 20 == version 1
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
case 24: // header length 24 == version 2 / 3
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
case 28: // header length 28 == version 4
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
|
||||
in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
if (eInfo == null) {
|
||||
return;
|
||||
}
|
||||
if (recent == null) {
|
||||
recent = eInfo;
|
||||
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
|
||||
recent = eInfo;
|
||||
}
|
||||
|
||||
if (filter.doFilter(eInfo)) {
|
||||
logcatPanel.log(eInfo);
|
||||
}
|
||||
events.add(eInfo);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get logcat message", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean reload() {
|
||||
stopLogcat();
|
||||
boolean ok = logcatPanel.clearLogcatArea();
|
||||
if (ok) {
|
||||
events.forEach((eInfo) -> {
|
||||
if (filter.doFilter(eInfo)) {
|
||||
logcatPanel.log(eInfo);
|
||||
}
|
||||
});
|
||||
startLogcat();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void clearEvents() {
|
||||
this.recent = null;
|
||||
this.events = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
stopLogcat();
|
||||
filter = new LogcatFilter(null, null);
|
||||
recent = null;
|
||||
}
|
||||
|
||||
public LogcatFilter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public class LogcatFilter {
|
||||
private final ArrayList<Integer> pid;
|
||||
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
|
||||
{
|
||||
add((byte) 1);
|
||||
add((byte) 2);
|
||||
add((byte) 3);
|
||||
add((byte) 4);
|
||||
add((byte) 5);
|
||||
add((byte) 6);
|
||||
add((byte) 7);
|
||||
add((byte) 8);
|
||||
}
|
||||
};
|
||||
|
||||
public LogcatFilter(ArrayList<Integer> pid, ArrayList<Byte> msgType) {
|
||||
if (pid != null) {
|
||||
this.pid = pid;
|
||||
} else {
|
||||
this.pid = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (msgType != null) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
}
|
||||
|
||||
public void addPid(int pid) {
|
||||
|
||||
if (!this.pid.contains(pid)) {
|
||||
this.pid.add(pid);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePid(int pid) {
|
||||
int pidPos = this.pid.indexOf(pid);
|
||||
if (pidPos >= 0) {
|
||||
this.pid.remove(pidPos);
|
||||
}
|
||||
}
|
||||
|
||||
public void togglePid(int pid, boolean state) {
|
||||
if (state) {
|
||||
addPid(pid);
|
||||
} else {
|
||||
removePid(pid);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMsgType(byte msgType) {
|
||||
if (!this.msgType.contains(msgType)) {
|
||||
this.msgType.add(msgType);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMsgType(byte msgType) {
|
||||
int typePos = this.msgType.indexOf(msgType);
|
||||
if (typePos >= 0) {
|
||||
this.msgType.remove(typePos);
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleMsgType(byte msgType, boolean state) {
|
||||
if (state) {
|
||||
addMsgType(msgType);
|
||||
} else {
|
||||
removeMsgType(msgType);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean doFilter(LogcatInfo inInfo) {
|
||||
if (pid.contains(inInfo.getPid())) {
|
||||
return msgType.contains(inInfo.getMsgType());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
|
||||
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
|
||||
inInfoList.forEach((inInfo) -> {
|
||||
if (doFilter(inInfo)) {
|
||||
outInfoList.add(inInfo);
|
||||
}
|
||||
});
|
||||
return outInfoList;
|
||||
}
|
||||
}
|
||||
|
||||
public class LogcatInfo {
|
||||
private String msg;
|
||||
private final byte msgType;
|
||||
private final int nsec;
|
||||
private final int pid;
|
||||
private final int sec;
|
||||
private final int tid;
|
||||
private final short hdrSize;
|
||||
private final short len;
|
||||
private final short version;
|
||||
private int lid;
|
||||
private int uid;
|
||||
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.version = 1;
|
||||
}
|
||||
|
||||
// Version 2 and 3 both have the same arguments
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.lid = lid;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.version = 3;
|
||||
}
|
||||
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.lid = lid;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.uid = uid;
|
||||
this.version = 4;
|
||||
}
|
||||
|
||||
public void setMsg(byte[] msg) {
|
||||
this.msg = new String(msg);
|
||||
}
|
||||
|
||||
public short getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public short getLen() {
|
||||
return this.len;
|
||||
}
|
||||
|
||||
public short getHeaderLen() {
|
||||
return this.hdrSize;
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return this.pid;
|
||||
}
|
||||
|
||||
public int getTid() {
|
||||
return this.tid;
|
||||
}
|
||||
|
||||
public int getSec() {
|
||||
return this.sec;
|
||||
}
|
||||
|
||||
public int getNSec() {
|
||||
return this.nsec;
|
||||
}
|
||||
|
||||
public int getLid() {
|
||||
return this.lid;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
return this.uid;
|
||||
}
|
||||
|
||||
public Instant getInstant() {
|
||||
return Instant.ofEpochSecond(getSec(), getNSec());
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
|
||||
return dtFormat.format(getInstant());
|
||||
}
|
||||
|
||||
public String getAfterTimestamp() {
|
||||
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
|
||||
return dtFormat.format(getInstant().plusMillis(1));
|
||||
}
|
||||
|
||||
public byte getMsgType() {
|
||||
return this.msgType;
|
||||
}
|
||||
|
||||
public String getMsgTypeString() {
|
||||
switch (getMsgType()) {
|
||||
case 0:
|
||||
return "Unknown";
|
||||
case 1:
|
||||
return "Default";
|
||||
case 2:
|
||||
return "Verbose";
|
||||
case 3:
|
||||
return "Debug";
|
||||
case 4:
|
||||
return "Info";
|
||||
case 5:
|
||||
return "Warn";
|
||||
case 6:
|
||||
return "Error";
|
||||
case 7:
|
||||
return "Fatal";
|
||||
case 8:
|
||||
return "Silent";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import io.github.hqktech.JDWP;
|
||||
import io.github.skylot.jdwp.JDWP;
|
||||
|
||||
public enum RuntimeType {
|
||||
ARRAY(91, "[]"),
|
||||
|
||||
@@ -21,56 +21,56 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hqktech.JDWP;
|
||||
import io.github.hqktech.JDWP.ArrayReference.Length.LengthReplyData;
|
||||
import io.github.hqktech.JDWP.ByteBuffer;
|
||||
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.EventData;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.ClassMatchRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.CountRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.LocationOnlyRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.StepRequest;
|
||||
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
|
||||
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
|
||||
import io.github.hqktech.JDWP.ObjectReference;
|
||||
import io.github.hqktech.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
|
||||
import io.github.hqktech.JDWP.ObjectReference.SetValues.FieldValueSetter;
|
||||
import io.github.hqktech.JDWP.Packet;
|
||||
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.Signature.SignatureReplyData;
|
||||
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesReplyData;
|
||||
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesSlots;
|
||||
import io.github.hqktech.JDWP.StackFrame.SetValues.SlotValueSetter;
|
||||
import io.github.hqktech.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
|
||||
import io.github.hqktech.JDWP.StringReference.Value.ValueReplyData;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyData;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Name.NameReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
||||
import io.github.skylot.jdwp.JDWP;
|
||||
import io.github.skylot.jdwp.JDWP.ArrayReference.Length.LengthReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ByteBuffer;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.EventData;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.ClassMatchRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.CountRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.LocationOnlyRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.StepRequest;
|
||||
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference.SetValues.FieldValueSetter;
|
||||
import io.github.skylot.jdwp.JDWP.Packet;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.Signature.SignatureReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesSlots;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.SetValues.SlotValueSetter;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StringReference.Value.ValueReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Name.NameReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ADB {
|
||||
}
|
||||
return result;
|
||||
} catch (SocketException e) {
|
||||
LOG.error("Aborting readServiceProtocol: socket closed");
|
||||
LOG.warn("Aborting readServiceProtocol: {}", e.toString());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to read readServiceProtocol", e);
|
||||
}
|
||||
@@ -207,7 +207,7 @@ public class ADB {
|
||||
List<ADBDeviceInfo> deviceInfoList = new ArrayList<>(deviceLines.length);
|
||||
for (String deviceLine : deviceLines) {
|
||||
if (!deviceLine.trim().isEmpty()) {
|
||||
deviceInfoList.add(ADBDeviceInfo.make(deviceLine, host, port));
|
||||
deviceInfoList.add(new ADBDeviceInfo(deviceLine, host, port));
|
||||
}
|
||||
}
|
||||
listener.onDeviceStatusChange(deviceInfoList);
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,7 +27,7 @@ public class ADBDevice {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class);
|
||||
|
||||
private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
|
||||
|
||||
private static final Pattern TIMESTAMP_FORMAT = Pattern.compile("^[0-9]{2}\\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}$");
|
||||
ADBDeviceInfo info;
|
||||
String androidReleaseVer;
|
||||
volatile Socket jdwpListenerSock;
|
||||
@@ -39,7 +41,10 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
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) {
|
||||
this.info = info;
|
||||
}
|
||||
@@ -47,21 +52,21 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public String getSerial() {
|
||||
return info.serial;
|
||||
return info.getSerial();
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
cmd = String.format("%04x%s", cmd.length(), cmd);
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
ForwardResult rst;
|
||||
if (ADB.setSerial(info.serial, outputStream, inputStream)) {
|
||||
if (ADB.setSerial(info.getSerial(), outputStream, inputStream)) {
|
||||
outputStream.write(cmd.getBytes());
|
||||
if (!ADB.isOkay(inputStream)) {
|
||||
rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream));
|
||||
@@ -99,9 +104,9 @@ public class ADBDevice {
|
||||
*/
|
||||
public int launchApp(String fullAppName) throws IOException, InterruptedException {
|
||||
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;
|
||||
res = ADB.execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
res = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
if (res == null) {
|
||||
return -1;
|
||||
}
|
||||
@@ -117,6 +122,49 @@ public class ADBDevice {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat
|
||||
*/
|
||||
public byte[] getBinaryLogcat() throws IOException {
|
||||
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "logcat -dB";
|
||||
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat after provided timestamp
|
||||
* Timestamp is in the format 09-08 02:18:03.131
|
||||
*/
|
||||
public byte[] getBinaryLogcat(String timestamp) throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
Matcher matcher = TIMESTAMP_FORMAT.matcher(timestamp);
|
||||
if (!matcher.find()) {
|
||||
LOG.error("Invalid Logcat Timestamp " + timestamp);
|
||||
}
|
||||
String cmd = "logcat -dB -t \"" + timestamp + "\"";
|
||||
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat -c
|
||||
*/
|
||||
public void clearLogcat() throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "logcat -c";
|
||||
ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timezone for the attached android device
|
||||
*/
|
||||
public String getTimezone() throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "getprop persist.sys.timezone";
|
||||
byte[] tz = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
return new String(tz).trim();
|
||||
}
|
||||
|
||||
public String getAndroidReleaseVersion() {
|
||||
if (!StringUtils.isEmpty(androidReleaseVer)) {
|
||||
return androidReleaseVer;
|
||||
@@ -134,13 +182,13 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
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();
|
||||
String cmd = "getprop";
|
||||
if (!StringUtils.isEmpty(entry)) {
|
||||
cmd += " " + entry;
|
||||
}
|
||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
||||
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||
socket.getOutputStream(), socket.getInputStream());
|
||||
if (payload != null) {
|
||||
props = new ArrayList<>();
|
||||
@@ -157,18 +205,18 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public List<Process> getProcessByPkg(String pkg) throws IOException {
|
||||
return getProcessList("ps | grep " + pkg, 0);
|
||||
return getProcessList("ps | grep " + pkg);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Process> getProcessList() throws IOException {
|
||||
return getProcessList("ps", 1);
|
||||
return getProcessList("ps");
|
||||
}
|
||||
|
||||
private List<Process> getProcessList(String cmd, int index) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
||||
private List<Process> getProcessList(String cmd) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
List<Process> procs = new ArrayList<>();
|
||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
||||
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||
socket.getOutputStream(), socket.getInputStream());
|
||||
if (payload != null) {
|
||||
String ps = new String(payload);
|
||||
@@ -194,10 +242,10 @@ public class ADBDevice {
|
||||
if (this.jdwpListenerSock != null) {
|
||||
return false;
|
||||
}
|
||||
jdwpListenerSock = ADB.connect(this.info.adbHost, this.info.adbPort);
|
||||
jdwpListenerSock = ADB.connect(this.info.getAdbHost(), this.info.getAdbPort());
|
||||
InputStream inputStream = jdwpListenerSock.getInputStream();
|
||||
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)) {
|
||||
Executors.newFixedThreadPool(1).execute(() -> {
|
||||
for (;;) {
|
||||
@@ -244,19 +292,20 @@ public class ADBDevice {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return info.serial.hashCode();
|
||||
return info.getSerial().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.allInfo;
|
||||
return info.getAllInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,91 @@
|
||||
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 String adbHost;
|
||||
public int adbPort;
|
||||
public String serial;
|
||||
public String state;
|
||||
public String model;
|
||||
public String allInfo;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ADBDeviceInfo.class);
|
||||
private final String adbHost;
|
||||
private final int adbPort;
|
||||
private final String serial;
|
||||
private final String state;
|
||||
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() {
|
||||
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
|
||||
public String toString() {
|
||||
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);
|
||||
// treat UI task operations as part of the task to not mix with others
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
task.onFinish(this);
|
||||
progressPane.setVisible(false);
|
||||
task.onFinish(this);
|
||||
});
|
||||
} finally {
|
||||
taskComplete(id);
|
||||
@@ -190,13 +190,21 @@ public class BackgroundExecutor {
|
||||
performCancel(executor);
|
||||
return cancelStatus;
|
||||
}
|
||||
updateProgress(executor);
|
||||
k++;
|
||||
Thread.sleep(k < 10 ? 200 : 1000); // faster update for short tasks
|
||||
if (jobsCount == 1 && k == 3) {
|
||||
if (k < 10) {
|
||||
// faster update for short tasks
|
||||
Thread.sleep(200);
|
||||
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
|
||||
progressPane.changeVisibility(this, true);
|
||||
}
|
||||
k++;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.debug("Task wait interrupted");
|
||||
@@ -210,7 +218,7 @@ public class BackgroundExecutor {
|
||||
}
|
||||
|
||||
private void updateProgress(ThreadPoolExecutor executor) {
|
||||
Consumer<ITaskProgress> onProgressListener = task.getOnProgressListener();
|
||||
Consumer<ITaskProgress> onProgressListener = task.getProgressListener();
|
||||
ITaskProgress taskProgress = task.getTaskProgress();
|
||||
if (taskProgress == null) {
|
||||
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||
@@ -231,13 +239,16 @@ public class BackgroundExecutor {
|
||||
// force termination
|
||||
task.cancel();
|
||||
executor.shutdown();
|
||||
if (executor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
int cancelTimeout = task.getCancelTimeoutMS();
|
||||
if (cancelTimeout != 0) {
|
||||
if (executor.awaitTermination(cancelTimeout, TimeUnit.MILLISECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.debug("Forcing tasks cancel");
|
||||
executor.shutdownNow();
|
||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
boolean complete = executor.awaitTermination(task.getShutdownTimeoutMS(), TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Forced task cancel status: {}",
|
||||
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
||||
}
|
||||
@@ -301,5 +312,13 @@ public class BackgroundExecutor {
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TaskWorker{status=" + status
|
||||
+ ", jobsCount=" + jobsCount
|
||||
+ ", jobsComplete=" + jobsComplete
|
||||
+ ", time=" + time + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user