Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21e94d8d5c | |||
| 7b1c7b967a | |||
| e4b19ab560 | |||
| 49137c9751 | |||
| 0606c90f22 | |||
| 65ade379a6 | |||
| a06df187c9 | |||
| e784c7f7df | |||
| a717392379 | |||
| a71b3a71d8 | |||
| 3366bf3dec | |||
| a505534197 | |||
| 357706b070 | |||
| e02434d135 | |||
| 4586015fc0 | |||
| 1832f2aee3 |
+2
-1
@@ -35,4 +35,5 @@ jadx-output/
|
|||||||
*.orig
|
*.orig
|
||||||
quark.json
|
quark.json
|
||||||
|
|
||||||
cliff.toml
|
cliff.toml
|
||||||
|
jadx-gui/src/main/resources/logback.xml
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||||
id 'com.diffplug.spotless' version '6.4.2'
|
id 'com.diffplug.spotless' version '6.5.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
@@ -32,7 +32,7 @@ allprojects {
|
|||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
testImplementation 'org.mockito:mockito-core:4.4.0'
|
testImplementation 'org.mockito:mockito-core:4.5.1'
|
||||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class JadxCLI {
|
|||||||
} catch (JadxArgsValidateException e) {
|
} catch (JadxArgsValidateException e) {
|
||||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
result = 1;
|
result = 1;
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
LOG.error("Process error:", e);
|
LOG.error("Process error:", e);
|
||||||
result = 1;
|
result = 1;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
|
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
testImplementation(project(':jadx-plugins:jadx-dex-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package jadx.api;
|
|
||||||
|
|
||||||
public final class CodePosition {
|
|
||||||
|
|
||||||
private final int line;
|
|
||||||
private final int offset;
|
|
||||||
private final int pos;
|
|
||||||
|
|
||||||
public CodePosition(int line, int offset, int pos) {
|
|
||||||
this.line = line;
|
|
||||||
this.offset = offset;
|
|
||||||
this.pos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CodePosition(int line) {
|
|
||||||
this(line, 0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public CodePosition(int line, int offset) {
|
|
||||||
this(line, offset, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPos() {
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine() {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CodePosition that = (CodePosition) o;
|
|
||||||
return line == that.line && offset == that.offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return line + 31 * offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(line);
|
|
||||||
if (offset != 0) {
|
|
||||||
sb.append(':').append(offset);
|
|
||||||
}
|
|
||||||
if (pos > 0) {
|
|
||||||
sb.append('@').append(pos);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface ICodeCache {
|
public interface ICodeCache extends Closeable {
|
||||||
|
|
||||||
void add(String clsFullName, ICodeInfo codeInfo);
|
void add(String clsFullName, ICodeInfo codeInfo);
|
||||||
|
|
||||||
void remove(String clsFullName);
|
void remove(String clsFullName);
|
||||||
|
|
||||||
@Nullable
|
@NotNull
|
||||||
ICodeInfo get(String clsFullName);
|
ICodeInfo get(String clsFullName);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getCode(String clsFullName);
|
||||||
|
|
||||||
|
boolean contains(String clsFullName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jadx.api.impl.SimpleCodeInfo;
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
|
||||||
public interface ICodeInfo {
|
public interface ICodeInfo {
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ public interface ICodeInfo {
|
|||||||
|
|
||||||
String getCodeStr();
|
String getCodeStr();
|
||||||
|
|
||||||
Map<Integer, Integer> getLineMapping();
|
ICodeMetadata getCodeMetadata();
|
||||||
|
|
||||||
Map<CodePosition, Object> getAnnotations();
|
boolean hasMetadata();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package jadx.api;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
public interface ICodeWriter {
|
public interface ICodeWriter {
|
||||||
String NL = System.getProperty("line.separator");
|
String NL = System.getProperty("line.separator");
|
||||||
@@ -38,13 +41,21 @@ public interface ICodeWriter {
|
|||||||
|
|
||||||
void setIndent(int indent);
|
void setIndent(int indent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current line (only if metadata is supported)
|
||||||
|
*/
|
||||||
int getLine();
|
int getLine();
|
||||||
|
|
||||||
void attachDefinition(ILineAttributeNode obj);
|
/**
|
||||||
|
* Return start line position (only if metadata is supported)
|
||||||
|
*/
|
||||||
|
int getLineStartPos();
|
||||||
|
|
||||||
void attachAnnotation(Object obj);
|
void attachDefinition(ICodeNodeRef obj);
|
||||||
|
|
||||||
void attachLineAnnotation(Object obj);
|
void attachAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
|
void attachLineAnnotation(ICodeAnnotation obj);
|
||||||
|
|
||||||
void attachSourceLine(int sourceLine);
|
void attachSourceLine(int sourceLine);
|
||||||
|
|
||||||
@@ -56,5 +67,6 @@ public interface ICodeWriter {
|
|||||||
|
|
||||||
StringBuilder getRawBuf();
|
StringBuilder getRawBuf();
|
||||||
|
|
||||||
Map<CodePosition, Object> getRawAnnotations();
|
@ApiStatus.Internal
|
||||||
|
Map<Integer, ICodeAnnotation> getRawAnnotations();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -15,6 +16,7 @@ import jadx.api.args.DeobfuscationMapFileMode;
|
|||||||
import jadx.api.data.ICodeData;
|
import jadx.api.data.ICodeData;
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.InMemoryCodeCache;
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
public class JadxArgs {
|
public class JadxArgs {
|
||||||
|
|
||||||
@@ -55,6 +57,11 @@ public class JadxArgs {
|
|||||||
*/
|
*/
|
||||||
private Predicate<String> classFilter = null;
|
private Predicate<String> classFilter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save dependencies for classes accepted by {@code classFilter}
|
||||||
|
*/
|
||||||
|
private boolean includeDependencies = false;
|
||||||
|
|
||||||
private boolean deobfuscationOn = false;
|
private boolean deobfuscationOn = false;
|
||||||
private boolean useSourceNameAsClassAlias = false;
|
private boolean useSourceNameAsClassAlias = false;
|
||||||
private boolean parseKotlinMetadata = false;
|
private boolean parseKotlinMetadata = false;
|
||||||
@@ -261,6 +268,14 @@ public class JadxArgs {
|
|||||||
this.skipSources = skipSources;
|
this.skipSources = skipSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIncludeDependencies(boolean includeDependencies) {
|
||||||
|
this.includeDependencies = includeDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIncludeDependencies() {
|
||||||
|
return includeDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
public Predicate<String> getClassFilter() {
|
public Predicate<String> getClassFilter() {
|
||||||
return classFilter;
|
return classFilter;
|
||||||
}
|
}
|
||||||
@@ -501,6 +516,21 @@ public class JadxArgs {
|
|||||||
this.pluginOptions = pluginOptions;
|
this.pluginOptions = pluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash of all options that can change result code
|
||||||
|
*/
|
||||||
|
public String makeCodeArgsHash() {
|
||||||
|
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||||
|
+ inlineAnonymousClasses + inlineMethods
|
||||||
|
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||||
|
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||||
|
+ insertDebugLines + extractFinally
|
||||||
|
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||||
|
+ respectBytecodeAccModifiers + fsCaseSensitive + renameFlags
|
||||||
|
+ commentsLevel + useDxInput + pluginOptions;
|
||||||
|
return FileUtils.md5Sum(argStr.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||||
@@ -513,12 +543,15 @@ public class JadxArgs {
|
|||||||
+ ", useImports=" + useImports
|
+ ", useImports=" + useImports
|
||||||
+ ", skipResources=" + skipResources
|
+ ", skipResources=" + skipResources
|
||||||
+ ", skipSources=" + skipSources
|
+ ", skipSources=" + skipSources
|
||||||
|
+ ", includeDependencies=" + includeDependencies
|
||||||
+ ", deobfuscationOn=" + deobfuscationOn
|
+ ", deobfuscationOn=" + deobfuscationOn
|
||||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||||
|
+ ", insertDebugLines=" + insertDebugLines
|
||||||
|
+ ", extractFinally=" + extractFinally
|
||||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||||
+ ", escapeUnicode=" + escapeUnicode
|
+ ", escapeUnicode=" + escapeUnicode
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ public class JadxArgsValidator {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||||
|
|
||||||
public static void validate(JadxArgs args) {
|
public static void validate(JadxDecompiler jadx) {
|
||||||
checkInputFiles(args);
|
JadxArgs args = jadx.getArgs();
|
||||||
|
checkInputFiles(jadx, args);
|
||||||
validateOutDirs(args);
|
validateOutDirs(args);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
@@ -22,9 +23,9 @@ public class JadxArgsValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkInputFiles(JadxArgs args) {
|
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
|
||||||
List<File> inputFiles = args.getInputFiles();
|
List<File> inputFiles = args.getInputFiles();
|
||||||
if (inputFiles.isEmpty()) {
|
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
|
||||||
throw new JadxArgsValidateException("Please specify input file");
|
throw new JadxArgsValidateException("Please specify input file");
|
||||||
}
|
}
|
||||||
for (File inputFile : inputFiles) {
|
for (File inputFile : inputFiles) {
|
||||||
@@ -66,19 +67,22 @@ public class JadxArgsValidator {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static File makeDirFromInput(JadxArgs args) {
|
private static File makeDirFromInput(JadxArgs args) {
|
||||||
File outDir;
|
|
||||||
String outDirName;
|
String outDirName;
|
||||||
File file = args.getInputFiles().get(0);
|
List<File> inputFiles = args.getInputFiles();
|
||||||
String name = file.getName();
|
if (inputFiles.isEmpty()) {
|
||||||
int pos = name.lastIndexOf('.');
|
outDirName = JadxArgs.DEFAULT_OUT_DIR;
|
||||||
if (pos != -1) {
|
|
||||||
outDirName = name.substring(0, pos);
|
|
||||||
} else {
|
} else {
|
||||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
File file = inputFiles.get(0);
|
||||||
|
String name = file.getName();
|
||||||
|
int pos = name.lastIndexOf('.');
|
||||||
|
if (pos != -1) {
|
||||||
|
outDirName = name.substring(0, pos);
|
||||||
|
} else {
|
||||||
|
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG.info("output directory: {}", outDirName);
|
LOG.info("output directory: {}", outDirName);
|
||||||
outDir = new File(outDirName);
|
return new File(outDirName);
|
||||||
return outDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkFile(File file) {
|
private static void checkFile(File file) {
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
import jadx.api.plugins.JadxPlugin;
|
import jadx.api.plugins.JadxPlugin;
|
||||||
import jadx.api.plugins.JadxPluginManager;
|
import jadx.api.plugins.JadxPluginManager;
|
||||||
import jadx.api.plugins.input.JadxInputPlugin;
|
import jadx.api.plugins.input.JadxInputPlugin;
|
||||||
@@ -35,7 +39,6 @@ import jadx.core.Jadx;
|
|||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
import jadx.core.dex.attributes.nodes.InlinedAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -98,6 +101,8 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
|
|
||||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
||||||
|
|
||||||
|
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||||
|
|
||||||
public JadxDecompiler() {
|
public JadxDecompiler() {
|
||||||
this(new JadxArgs());
|
this(new JadxArgs());
|
||||||
}
|
}
|
||||||
@@ -108,7 +113,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
|
|
||||||
public void load() {
|
public void load() {
|
||||||
reset();
|
reset();
|
||||||
JadxArgsValidator.validate(args);
|
JadxArgsValidator.validate(this);
|
||||||
LOG.info("loading ...");
|
LOG.info("loading ...");
|
||||||
loadPlugins(args);
|
loadPlugins(args);
|
||||||
loadInputFiles();
|
loadInputFiles();
|
||||||
@@ -132,11 +137,20 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
loadedInputs.add(loadResult);
|
loadedInputs.add(loadResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadedInputs.addAll(customLoads);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addCustomLoad(ILoadResult customLoad) {
|
||||||
|
customLoads.add(customLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ILoadResult> getCustomLoads() {
|
||||||
|
return customLoads;
|
||||||
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
root = null;
|
root = null;
|
||||||
classes = null;
|
classes = null;
|
||||||
@@ -229,6 +243,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
save(false, true);
|
save(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
private void save(boolean saveSources, boolean saveResources) {
|
private void save(boolean saveSources, boolean saveResources) {
|
||||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||||
ex.shutdown();
|
ex.shutdown();
|
||||||
@@ -320,10 +335,14 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
List<JavaClass> classes = getClasses();
|
List<JavaClass> classes = getClasses();
|
||||||
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
List<JavaClass> processQueue = new ArrayList<>(classes.size());
|
||||||
for (JavaClass cls : classes) {
|
for (JavaClass cls : classes) {
|
||||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
ClassNode clsNode = cls.getClassNode();
|
||||||
|
if (clsNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) {
|
||||||
|
if (!args.isIncludeDependencies()) {
|
||||||
|
clsNode.add(AFlag.DONT_GENERATE);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
processQueue.add(cls);
|
processQueue.add(cls);
|
||||||
@@ -353,14 +372,14 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
if (classes == null) {
|
if (classes == null) {
|
||||||
List<ClassNode> classNodeList = root.getClasses(false);
|
List<ClassNode> classNodeList = root.getClasses();
|
||||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||||
classesMap.clear();
|
|
||||||
for (ClassNode classNode : classNodeList) {
|
for (ClassNode classNode : classNodeList) {
|
||||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
if (classNode.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = new JavaClass(classNode, this);
|
continue;
|
||||||
clsList.add(javaClass);
|
}
|
||||||
classesMap.put(classNode, javaClass);
|
if (!classNode.getClassInfo().isInner()) {
|
||||||
|
clsList.add(convertClassNode(classNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
classes = Collections.unmodifiableList(clsList);
|
classes = Collections.unmodifiableList(clsList);
|
||||||
@@ -368,6 +387,10 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JavaClass> getClassesWithInners() {
|
||||||
|
return Utils.collectionMap(root.getClasses(), this::convertClassNode);
|
||||||
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getResources() {
|
public List<ResourceFile> getResources() {
|
||||||
if (resources == null) {
|
if (resources == null) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
@@ -425,6 +448,7 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* Internal API. Not Stable!
|
* Internal API. Not Stable!
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
public RootNode getRoot() {
|
public RootNode getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -504,8 +528,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
public JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||||
JavaMethod javaMethod = methodsMap.get(mth);
|
JavaMethod javaMethod = methodsMap.get(mth);
|
||||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
||||||
return javaMethod;
|
return javaMethod;
|
||||||
@@ -545,8 +570,9 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
return getCodeParentClass(codeCls);
|
return getCodeParentClass(codeCls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
@Nullable
|
@Nullable
|
||||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
public JavaField getJavaFieldByNode(FieldNode fld) {
|
||||||
JavaField javaField = fieldsMap.get(fld);
|
JavaField javaField = fieldsMap.get(fld);
|
||||||
if (javaField != null && javaField.getFieldNode() == fld) {
|
if (javaField != null && javaField.getFieldNode() == fld) {
|
||||||
return javaField;
|
return javaField;
|
||||||
@@ -611,81 +637,84 @@ public final class JadxDecompiler implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
JavaNode convertNode(Object obj) {
|
public JavaNode getJavaNodeByRef(ICodeNodeRef ann) {
|
||||||
if (obj instanceof VarRef) {
|
return getJavaNodeByCodeAnnotation(null, ann);
|
||||||
VarRef varRef = (VarRef) obj;
|
}
|
||||||
MethodNode mthNode = varRef.getMth();
|
|
||||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
@Nullable
|
||||||
if (mth == null) {
|
public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) {
|
||||||
|
if (ann == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (ann.getAnnType()) {
|
||||||
|
case CLASS:
|
||||||
|
return convertClassNode((ClassNode) ann);
|
||||||
|
case METHOD:
|
||||||
|
return getJavaMethodByNode((MethodNode) ann);
|
||||||
|
case FIELD:
|
||||||
|
return getJavaFieldByNode((FieldNode) ann);
|
||||||
|
case DECLARATION:
|
||||||
|
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||||
|
case VAR:
|
||||||
|
return resolveVarNode((VarNode) ann);
|
||||||
|
case VAR_REF:
|
||||||
|
return resolveVarRef(codeInfo, (VarRef) ann);
|
||||||
|
case OFFSET:
|
||||||
|
// offset annotation don't have java node object
|
||||||
return null;
|
return null;
|
||||||
}
|
default:
|
||||||
return new JavaVariable(mth, varRef);
|
throw new JadxRuntimeException("Unknown annotation type: " + ann.getAnnType() + ", class: " + ann.getClass());
|
||||||
}
|
}
|
||||||
if (!(obj instanceof LineAttrNode)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
LineAttrNode node = (LineAttrNode) obj;
|
|
||||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (obj instanceof ClassNode) {
|
|
||||||
return convertClassNode((ClassNode) obj);
|
|
||||||
}
|
|
||||||
if (obj instanceof MethodNode) {
|
|
||||||
return getJavaMethodByNode(((MethodNode) obj));
|
|
||||||
}
|
|
||||||
if (obj instanceof FieldNode) {
|
|
||||||
return getJavaFieldByNode((FieldNode) obj);
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make interface for all nodes in code annotations and add common method instead this
|
@Nullable
|
||||||
Object getInternalNode(JavaNode javaNode) {
|
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||||
if (javaNode instanceof JavaClass) {
|
MethodNode mthNode = varNode.getMth();
|
||||||
return ((JavaClass) javaNode).getClassNode();
|
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||||
|
if (mth == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if (javaNode instanceof JavaMethod) {
|
return new JavaVariable(mth, varNode);
|
||||||
return ((JavaMethod) javaNode).getMethodNode();
|
|
||||||
}
|
|
||||||
if (javaNode instanceof JavaField) {
|
|
||||||
return ((JavaField) javaNode).getFieldNode();
|
|
||||||
}
|
|
||||||
if (javaNode instanceof JavaVariable) {
|
|
||||||
return ((JavaVariable) javaNode).getVarRef();
|
|
||||||
}
|
|
||||||
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
@Nullable
|
||||||
|
private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) {
|
||||||
|
if (codeInfo == null) {
|
||||||
|
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||||
|
}
|
||||||
|
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||||
|
if (varNodeAnn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (JavaVariable) getJavaNodeByCodeAnnotation(codeInfo, varNodeAnn);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||||
return nodesList.stream()
|
return nodesList.stream()
|
||||||
.map(this::convertNode)
|
.map(this::getJavaNodeByRef)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) {
|
||||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||||
if (map.isEmpty()) {
|
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Object obj = map.get(new CodePosition(line, offset));
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return convertNode(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) {
|
||||||
JavaClass jCls = javaNode.getTopParentClass();
|
ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos);
|
||||||
jCls.decompile();
|
return getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
int defLine = javaNode.getDecompiledLine();
|
}
|
||||||
if (defLine == 0) {
|
|
||||||
|
@Nullable
|
||||||
|
public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) {
|
||||||
|
ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos);
|
||||||
|
if (obj == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
return getJavaNodeByRef(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadCodeData() {
|
public void reloadCodeData() {
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
|
||||||
@@ -46,24 +49,21 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
ICodeInfo code = getCodeInfo();
|
return getCodeInfo().getCodeStr();
|
||||||
if (code == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return code.getCodeStr();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeInfo getCodeInfo() {
|
public @NotNull ICodeInfo getCodeInfo() {
|
||||||
|
load();
|
||||||
return cls.decompile();
|
return cls.decompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decompile() {
|
public void decompile() {
|
||||||
cls.decompile();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reload() {
|
public synchronized ICodeInfo reload() {
|
||||||
listsLoaded = false;
|
listsLoaded = false;
|
||||||
cls.reloadCode();
|
return cls.reloadCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unload() {
|
public void unload() {
|
||||||
@@ -75,6 +75,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.contains(AFlag.DONT_GENERATE);
|
return cls.contains(AFlag.DONT_GENERATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInner() {
|
||||||
|
return cls.isInner();
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getSmali() {
|
public synchronized String getSmali() {
|
||||||
return cls.getDisassembledCode();
|
return cls.getDisassembledCode();
|
||||||
}
|
}
|
||||||
@@ -87,13 +91,22 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadLists() {
|
/**
|
||||||
|
* Decompile class and loads internal lists of fields, methods, etc.
|
||||||
|
* Do nothing if already loaded.
|
||||||
|
* Return not null on first call only (for actual loading)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private synchronized void load() {
|
||||||
if (listsLoaded) {
|
if (listsLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listsLoaded = true;
|
listsLoaded = true;
|
||||||
decompile();
|
|
||||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||||
|
ICodeCache codeCache = rootDecompiler.getArgs().getCodeCache();
|
||||||
|
if (!codeCache.contains(cls.getRawName())) {
|
||||||
|
cls.decompile();
|
||||||
|
}
|
||||||
|
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
@@ -101,7 +114,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.loadLists();
|
javaClass.load();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +125,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
|
||||||
for (ClassNode inner : cls.getInlinedClasses()) {
|
for (ClassNode inner : cls.getInlinedClasses()) {
|
||||||
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
|
||||||
javaClass.loadLists();
|
javaClass.load();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
this.inlinedClasses = Collections.unmodifiableList(list);
|
this.inlinedClasses = Collections.unmodifiableList(list);
|
||||||
@@ -122,10 +135,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (fieldsCount != 0) {
|
if (fieldsCount != 0) {
|
||||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
// if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaField javaField = new JavaField(this, f);
|
JavaField javaField = new JavaField(this, f);
|
||||||
flds.add(javaField);
|
flds.add(javaField);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
this.fields = Collections.unmodifiableList(flds);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
}
|
}
|
||||||
@@ -151,47 +164,45 @@ public final class JavaClass implements JavaNode {
|
|||||||
return decompiler;
|
return decompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
public ICodeAnnotation getAnnotationAt(int pos) {
|
||||||
ICodeInfo code = getCodeInfo();
|
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||||
if (code == null) {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
return code.getAnnotations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getAnnotationAt(CodePosition pos) {
|
public Map<Integer, JavaNode> getUsageMap() {
|
||||||
return getCodeAnnotations().get(pos);
|
Map<Integer, ICodeAnnotation> map = getCodeInfo().getCodeMetadata().getAsMap();
|
||||||
}
|
|
||||||
|
|
||||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
|
||||||
if (map.isEmpty() || decompiler == null) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
Map<Integer, JavaNode> resultMap = new HashMap<>(map.size());
|
||||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||||
CodePosition codePosition = entry.getKey();
|
int codePosition = entry.getKey();
|
||||||
Object obj = entry.getValue();
|
ICodeAnnotation obj = entry.getValue();
|
||||||
JavaNode node = getRootDecompiler().convertNode(obj);
|
if (obj instanceof ICodeNodeRef) {
|
||||||
if (node != null) {
|
JavaNode node = getRootDecompiler().getJavaNodeByRef((ICodeNodeRef) obj);
|
||||||
resultMap.put(codePosition, node);
|
if (node != null) {
|
||||||
|
resultMap.put(codePosition, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CodePosition> getUsageFor(JavaNode javaNode) {
|
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
Map<Integer, ICodeAnnotation> map = codeInfo.getCodeMetadata().getAsMap();
|
||||||
if (map.isEmpty() || decompiler == null) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
|
JadxDecompiler rootDec = getRootDecompiler();
|
||||||
List<CodePosition> result = new ArrayList<>();
|
List<Integer> result = new ArrayList<>();
|
||||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||||
CodePosition codePosition = entry.getKey();
|
ICodeAnnotation ann = entry.getValue();
|
||||||
Object obj = entry.getValue();
|
if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||||
if (internalNode.equals(obj)) {
|
// ignore declarations
|
||||||
result.add(codePosition);
|
continue;
|
||||||
|
}
|
||||||
|
JavaNode annNode = rootDec.getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||||
|
if (javaNode.equals(annNode)) {
|
||||||
|
result.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -202,20 +213,8 @@ public final class JavaClass implements JavaNode {
|
|||||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Deprecated
|
|
||||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
|
||||||
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Deprecated
|
|
||||||
public CodePosition getDefinitionPosition() {
|
|
||||||
return getRootDecompiler().getDefinitionPosition(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSourceLine(int decompiledLine) {
|
public Integer getSourceLine(int decompiledLine) {
|
||||||
return getCodeInfo().getLineMapping().get(decompiledLine);
|
return getCodeInfo().getCodeMetadata().getLineMapping().get(decompiledLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -261,22 +260,22 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInnerClasses() {
|
public List<JavaClass> getInnerClasses() {
|
||||||
loadLists();
|
load();
|
||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInlinedClasses() {
|
public List<JavaClass> getInlinedClasses() {
|
||||||
loadLists();
|
load();
|
||||||
return inlinedClasses;
|
return inlinedClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaField> getFields() {
|
public List<JavaField> getFields() {
|
||||||
loadLists();
|
load();
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaMethod> getMethods() {
|
public List<JavaMethod> getMethods() {
|
||||||
loadLists();
|
load();
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,11 +293,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
this.cls.getClassInfo().removeAlias();
|
this.cls.getClassInfo().removeAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return cls.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return cls.getDefPosition();
|
return cls.getDefPosition();
|
||||||
|
|||||||
@@ -50,11 +50,6 @@ public final class JavaField implements JavaNode {
|
|||||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return field.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return field.getDefPosition();
|
return field.getDefPosition();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public final class JavaMethod implements JavaNode {
|
|||||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||||
return ovrdAttr.getRelatedMthNodes().stream()
|
return ovrdAttr.getRelatedMthNodes().stream()
|
||||||
.map(m -> {
|
.map(m -> {
|
||||||
JavaMethod javaMth = (JavaMethod) decompiler.convertNode(m);
|
JavaMethod javaMth = decompiler.getJavaMethodByNode(m);
|
||||||
if (javaMth == null) {
|
if (javaMth == null) {
|
||||||
LOG.warn("Failed convert to java method: {}", m);
|
LOG.warn("Failed convert to java method: {}", m);
|
||||||
}
|
}
|
||||||
@@ -96,11 +96,6 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return mth.getMethodInfo().isClassInit();
|
return mth.getMethodInfo().isClassInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return mth.getDecompiledLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return mth.getDefPosition();
|
return mth.getDefPosition();
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ public interface JavaNode {
|
|||||||
|
|
||||||
JavaClass getTopParentClass();
|
JavaClass getTopParentClass();
|
||||||
|
|
||||||
int getDecompiledLine();
|
|
||||||
|
|
||||||
int getDefPos();
|
int getDefPos();
|
||||||
|
|
||||||
List<JavaNode> getUseIn();
|
List<JavaNode> getUseIn();
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.data.annotations.VarRef;
|
|
||||||
|
|
||||||
public class JavaVariable implements JavaNode {
|
public class JavaVariable implements JavaNode {
|
||||||
private final JavaMethod mth;
|
private final JavaMethod mth;
|
||||||
private final VarRef varRef;
|
private final VarNode varNode;
|
||||||
|
|
||||||
public JavaVariable(JavaMethod mth, VarRef varRef) {
|
public JavaVariable(JavaMethod mth, VarNode varNode) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.varRef = varRef;
|
this.varNode = varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JavaMethod getMth() {
|
public JavaMethod getMth() {
|
||||||
@@ -22,26 +21,26 @@ public class JavaVariable implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getReg() {
|
public int getReg() {
|
||||||
return varRef.getReg();
|
return varNode.getReg();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSsa() {
|
public int getSsa() {
|
||||||
return varRef.getSsa();
|
return varNode.getSsa();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return varRef.getName();
|
return varNode.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public VarRef getVarRef() {
|
public VarNode getVarNode() {
|
||||||
return varRef;
|
return varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
|
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,20 +53,9 @@ public class JavaVariable implements JavaNode {
|
|||||||
return mth.getTopParentClass();
|
return mth.getTopParentClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
if (varRef instanceof VarDeclareRef) {
|
|
||||||
return ((VarDeclareRef) varRef).getDecompiledLine();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefPos() {
|
public int getDefPos() {
|
||||||
if (varRef instanceof VarDeclareRef) {
|
return varNode.getDefPosition();
|
||||||
return ((VarDeclareRef) varRef).getDefPosition();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,7 +65,7 @@ public class JavaVariable implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return varRef.hashCode();
|
return varNode.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -88,6 +76,6 @@ public class JavaVariable implements JavaNode {
|
|||||||
if (!(o instanceof JavaVariable)) {
|
if (!(o instanceof JavaVariable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return varRef.equals(((JavaVariable) o).varRef);
|
return varNode.equals(((JavaVariable) o).varNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
public interface ICodeRawOffset {
|
|
||||||
int getOffset();
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
|
|
||||||
public class VarDeclareRef extends VarRef implements ILineAttributeNode {
|
|
||||||
|
|
||||||
public static VarDeclareRef get(MethodNode mth, CodeVar codeVar) {
|
|
||||||
VarDeclareRef ref = new VarDeclareRef(mth, codeVar);
|
|
||||||
codeVar.setCachedVarRef(ref);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int sourceLine;
|
|
||||||
private int decompiledLine;
|
|
||||||
private int defPosition;
|
|
||||||
|
|
||||||
private VarDeclareRef(MethodNode mth, CodeVar codeVar) {
|
|
||||||
super(mth, codeVar.getAnySsaVar());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSourceLine() {
|
|
||||||
return sourceLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSourceLine(int sourceLine) {
|
|
||||||
this.sourceLine = sourceLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDecompiledLine() {
|
|
||||||
return decompiledLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDecompiledLine(int decompiledLine) {
|
|
||||||
this.decompiledLine = decompiledLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDefPosition() {
|
|
||||||
return defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDefPosition(int pos) {
|
|
||||||
this.defPosition = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "VarDeclareRef{r" + getReg() + 'v' + getSsa() + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package jadx.api.data.annotations;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
|
||||||
import jadx.core.dex.instructions.args.CodeVar;
|
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
|
||||||
|
|
||||||
public class VarRef {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static VarRef get(MethodNode mth, RegisterArg reg) {
|
|
||||||
SSAVar ssaVar = reg.getSVar();
|
|
||||||
if (ssaVar == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
CodeVar codeVar = ssaVar.getCodeVar();
|
|
||||||
VarRef cachedVarRef = codeVar.getCachedVarRef();
|
|
||||||
if (cachedVarRef != null) {
|
|
||||||
if (cachedVarRef.getName() == null) {
|
|
||||||
cachedVarRef.setName(codeVar.getName());
|
|
||||||
}
|
|
||||||
return cachedVarRef;
|
|
||||||
}
|
|
||||||
VarRef newVarRef = new VarRef(mth, ssaVar);
|
|
||||||
codeVar.setCachedVarRef(newVarRef);
|
|
||||||
return newVarRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final MethodNode mth;
|
|
||||||
private final int reg;
|
|
||||||
private final int ssa;
|
|
||||||
private final ArgType type;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
protected VarRef(MethodNode mth, SSAVar ssaVar) {
|
|
||||||
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
|
||||||
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private VarRef(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
|
||||||
this.mth = mth;
|
|
||||||
this.reg = reg;
|
|
||||||
this.ssa = ssa;
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode getMth() {
|
|
||||||
return mth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReg() {
|
|
||||||
return reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSsa() {
|
|
||||||
return ssa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArgType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof VarRef)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
VarRef other = (VarRef) o;
|
|
||||||
return getReg() == other.getReg()
|
|
||||||
&& getSsa() == other.getSsa()
|
|
||||||
&& getMth().equals(other.getMth());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return 31 * getReg() + getSsa();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "VarUseRef{r" + reg + 'v' + ssa + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package jadx.api.data.impl;
|
|||||||
import jadx.api.JavaVariable;
|
import jadx.api.JavaVariable;
|
||||||
import jadx.api.data.CodeRefType;
|
import jadx.api.data.CodeRefType;
|
||||||
import jadx.api.data.IJavaCodeRef;
|
import jadx.api.data.IJavaCodeRef;
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
|
||||||
public class JadxCodeRef implements IJavaCodeRef {
|
public class JadxCodeRef implements IJavaCodeRef {
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ public class JadxCodeRef implements IJavaCodeRef {
|
|||||||
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
return forVar(javaVariable.getReg(), javaVariable.getSsa());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JadxCodeRef forVar(VarRef varRef) {
|
public static JadxCodeRef forVar(VarNode varNode) {
|
||||||
return forVar(varRef.getReg(), varRef.getSsa());
|
return forVar(varNode.getReg(), varNode.getSsa());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JadxCodeRef forCatch(int handlerOffset) {
|
public static JadxCodeRef forCatch(int handlerOffset) {
|
||||||
|
|||||||
@@ -2,23 +2,19 @@ package jadx.api.impl;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||||
|
|
||||||
public class AnnotatedCodeInfo implements ICodeInfo {
|
public class AnnotatedCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final Map<Integer, Integer> lineMapping;
|
private final ICodeMetadata metadata;
|
||||||
private final Map<CodePosition, Object> annotations;
|
|
||||||
|
|
||||||
public AnnotatedCodeInfo(ICodeInfo codeInfo) {
|
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<Integer, ICodeAnnotation> annotations) {
|
||||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnnotatedCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.lineMapping = lineMapping;
|
this.metadata = CodeMetadataStorage.build(lineMapping, annotations);
|
||||||
this.annotations = annotations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -27,13 +23,13 @@ public class AnnotatedCodeInfo implements ICodeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeMetadata getCodeMetadata() {
|
||||||
return lineMapping;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
public boolean hasMetadata() {
|
||||||
return annotations;
|
return metadata != ICodeMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,18 +5,20 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarRef;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
|
|
||||||
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter {
|
||||||
|
|
||||||
private int line = 1;
|
private int line = 1;
|
||||||
private int offset;
|
private int offset;
|
||||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
|
||||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
public AnnotatedCodeWriter() {
|
public AnnotatedCodeWriter() {
|
||||||
@@ -59,19 +61,17 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeWriter add(ICodeWriter cw) {
|
public ICodeWriter add(ICodeWriter cw) {
|
||||||
if ((!(cw instanceof AnnotatedCodeWriter))) {
|
if (!cw.isMetadataSupported()) {
|
||||||
buf.append(cw.getCodeStr());
|
buf.append(cw.getCodeStr());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||||
line--;
|
line--;
|
||||||
int startLine = line;
|
|
||||||
int startPos = getLength();
|
int startPos = getLength();
|
||||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : code.annotations.entrySet()) {
|
||||||
CodePosition codePos = entry.getKey();
|
int pos = entry.getKey();
|
||||||
int newLine = startLine + codePos.getLine();
|
int newPos = startPos + pos;
|
||||||
int newPos = startPos + codePos.getPos();
|
attachAnnotation(entry.getValue(), newPos);
|
||||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
|
||||||
}
|
}
|
||||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||||
@@ -101,44 +101,36 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DefinitionWrapper {
|
@Override
|
||||||
private final ILineAttributeNode node;
|
public int getLineStartPos() {
|
||||||
|
return getLength() - offset;
|
||||||
private DefinitionWrapper(ILineAttributeNode node) {
|
|
||||||
this.node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILineAttributeNode getNode() {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachDefinition(ILineAttributeNode obj) {
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj);
|
attachAnnotation(new NodeDeclareRef(obj));
|
||||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachAnnotation(Object obj) {
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
attachAnnotation(obj, getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachLineAnnotation(Object obj) {
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
attachAnnotation(obj, getLineStartPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
private void attachAnnotation(ICodeAnnotation obj, int pos) {
|
||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
annotations = new HashMap<>();
|
annotations = new HashMap<>();
|
||||||
}
|
}
|
||||||
@@ -164,29 +156,39 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
|||||||
public ICodeInfo finish() {
|
public ICodeInfo finish() {
|
||||||
removeFirstEmptyLine();
|
removeFirstEmptyLine();
|
||||||
processDefinitionAnnotations();
|
processDefinitionAnnotations();
|
||||||
|
validateAnnotations();
|
||||||
String code = buf.toString();
|
String code = buf.toString();
|
||||||
buf = null;
|
buf = null;
|
||||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getRawAnnotations() {
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDefinitionAnnotations() {
|
private void processDefinitionAnnotations() {
|
||||||
if (!annotations.isEmpty()) {
|
if (!annotations.isEmpty()) {
|
||||||
annotations.entrySet().removeIf(entry -> {
|
annotations.forEach((k, v) -> {
|
||||||
Object v = entry.getValue();
|
if (v instanceof NodeDeclareRef) {
|
||||||
if (v instanceof DefinitionWrapper) {
|
NodeDeclareRef declareRef = (NodeDeclareRef) v;
|
||||||
ILineAttributeNode l = ((DefinitionWrapper) v).getNode();
|
declareRef.setDefPos(k);
|
||||||
CodePosition codePos = entry.getKey();
|
declareRef.getNode().setDefPosition(k);
|
||||||
l.setDecompiledLine(codePos.getLine());
|
|
||||||
l.setDefPosition(codePos.getPos());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateAnnotations() {
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
annotations.values().removeIf(v -> {
|
||||||
|
if (v.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||||
|
VarRef varRef = (VarRef) v;
|
||||||
|
return varRef.getRefPos() == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
|
|
||||||
|
public abstract class DelegateCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
protected final ICodeCache backCache;
|
||||||
|
|
||||||
|
public DelegateCodeCache(ICodeCache backCache) {
|
||||||
|
this.backCache = backCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
|
backCache.add(clsFullName, codeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String clsFullName) {
|
||||||
|
backCache.remove(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ICodeInfo get(String clsFullName) {
|
||||||
|
return backCache.get(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getCode(String clsFullName) {
|
||||||
|
return backCache.getCode(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return backCache.contains(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
backCache.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
@@ -22,9 +24,33 @@ public class InMemoryCodeCache implements ICodeCache {
|
|||||||
storage.remove(clsFullName);
|
storage.remove(clsFullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeInfo get(String clsFullName) {
|
public ICodeInfo get(String clsFullName) {
|
||||||
return storage.get(clsFullName);
|
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||||
|
if (codeInfo == null) {
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
|
return codeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getCode(String clsFullName) {
|
||||||
|
ICodeInfo codeInfo = storage.get(clsFullName);
|
||||||
|
if (codeInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return codeInfo.getCodeStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return storage.containsKey(clsFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
storage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeCache;
|
import jadx.api.ICodeCache;
|
||||||
@@ -7,6 +8,8 @@ import jadx.api.ICodeInfo;
|
|||||||
|
|
||||||
public class NoOpCodeCache implements ICodeCache {
|
public class NoOpCodeCache implements ICodeCache {
|
||||||
|
|
||||||
|
public static final NoOpCodeCache INSTANCE = new NoOpCodeCache();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -18,10 +21,26 @@ public class NoOpCodeCache implements ICodeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ICodeInfo get(String clsFullName) {
|
@NotNull
|
||||||
|
public ICodeInfo get(String clsFullName) {
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getCode(String clsFullName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String clsFullName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "NoOpCodeCache";
|
return "NoOpCodeCache";
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package jadx.api.impl;
|
package jadx.api.impl;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
|
||||||
public class SimpleCodeInfo implements ICodeInfo {
|
public class SimpleCodeInfo implements ICodeInfo {
|
||||||
|
|
||||||
@@ -20,13 +17,13 @@ public class SimpleCodeInfo implements ICodeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeMetadata getCodeMetadata() {
|
||||||
return Collections.emptyMap();
|
return ICodeMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
public boolean hasMetadata() {
|
||||||
return Collections.emptyMap();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import java.util.Map;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.attributes.ILineAttributeNode;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,17 +193,22 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachDefinition(ILineAttributeNode obj) {
|
public int getLineStartPos() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachDefinition(ICodeNodeRef obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachAnnotation(Object obj) {
|
public void attachAnnotation(ICodeAnnotation obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachLineAnnotation(Object obj) {
|
public void attachLineAnnotation(ICodeAnnotation obj) {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +243,7 @@ public class SimpleCodeWriter implements ICodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<CodePosition, Object> getRawAnnotations() {
|
public Map<Integer, ICodeAnnotation> getRawAnnotations() {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
public interface ICodeAnnotation {
|
||||||
|
|
||||||
|
enum AnnType {
|
||||||
|
CLASS,
|
||||||
|
FIELD,
|
||||||
|
METHOD,
|
||||||
|
VAR,
|
||||||
|
VAR_REF,
|
||||||
|
DECLARATION,
|
||||||
|
OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
AnnType getAnnType();
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.impl.CodeMetadataStorage;
|
||||||
|
|
||||||
|
public interface ICodeMetadata {
|
||||||
|
|
||||||
|
ICodeMetadata EMPTY = CodeMetadataStorage.empty();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation getAt(int position);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation getClosestUp(int position);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate code annotations from {@code startPos} to smaller positions.
|
||||||
|
*
|
||||||
|
* @param visitor
|
||||||
|
* return not null value to stop iterations
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<T> T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate code annotations from {@code startPos} to higher positions.
|
||||||
|
*
|
||||||
|
* @param visitor
|
||||||
|
* return not null value to stop iterations
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<T> T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current node at position (can be enclosing class or method)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
ICodeNodeRef getNodeAt(int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any definition of class or method below position
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
ICodeNodeRef getNodeBelow(int position);
|
||||||
|
|
||||||
|
Map<Integer, ICodeAnnotation> getAsMap();
|
||||||
|
|
||||||
|
Map<Integer, Integer> getLineMapping();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.api.metadata;
|
||||||
|
|
||||||
|
public interface ICodeNodeRef extends ICodeAnnotation {
|
||||||
|
int getDefPosition();
|
||||||
|
|
||||||
|
void setDefPosition(int pos);
|
||||||
|
}
|
||||||
+8
-3
@@ -1,11 +1,12 @@
|
|||||||
package jadx.api.data.annotations;
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
|
|
||||||
public class InsnCodeOffset implements ICodeRawOffset {
|
public class InsnCodeOffset implements ICodeAnnotation {
|
||||||
|
|
||||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
@@ -40,11 +41,15 @@ public class InsnCodeOffset implements ICodeRawOffset {
|
|||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOffset() {
|
public int getOffset() {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "offset=" + offset;
|
return "offset=" + offset;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
|
||||||
|
public class NodeDeclareRef implements ICodeAnnotation {
|
||||||
|
|
||||||
|
private final ICodeNodeRef node;
|
||||||
|
|
||||||
|
private int defPos;
|
||||||
|
|
||||||
|
public NodeDeclareRef(ICodeNodeRef node) {
|
||||||
|
this.node = Objects.requireNonNull(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICodeNodeRef getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefPos() {
|
||||||
|
return defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefPos(int defPos) {
|
||||||
|
this.defPos = defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.DECLARATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NodeDeclareRef{" + node + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
|
import jadx.core.dex.instructions.args.CodeVar;
|
||||||
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable info
|
||||||
|
*/
|
||||||
|
public class VarNode implements ICodeNodeRef {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, RegisterArg reg) {
|
||||||
|
SSAVar ssaVar = reg.getSVar();
|
||||||
|
if (ssaVar == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return get(mth, ssaVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, CodeVar codeVar) {
|
||||||
|
return get(mth, codeVar.getAnySsaVar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static VarNode get(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
CodeVar codeVar = ssaVar.getCodeVar();
|
||||||
|
if (codeVar.isThis()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
VarNode cachedVarNode = codeVar.getCachedVarNode();
|
||||||
|
if (cachedVarNode != null) {
|
||||||
|
return cachedVarNode;
|
||||||
|
}
|
||||||
|
VarNode newVarNode = new VarNode(mth, ssaVar);
|
||||||
|
codeVar.setCachedVarNode(newVarNode);
|
||||||
|
return newVarNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ICodeAnnotation getRef(MethodNode mth, RegisterArg reg) {
|
||||||
|
VarNode varNode = get(mth, reg);
|
||||||
|
if (varNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return varNode.getVarRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MethodNode mth;
|
||||||
|
private final int reg;
|
||||||
|
private final int ssa;
|
||||||
|
private final ArgType type;
|
||||||
|
private @Nullable String name;
|
||||||
|
private int defPos;
|
||||||
|
|
||||||
|
private final VarRef varRef;
|
||||||
|
|
||||||
|
protected VarNode(MethodNode mth, SSAVar ssaVar) {
|
||||||
|
this(mth, ssaVar.getRegNum(), ssaVar.getVersion(),
|
||||||
|
ssaVar.getCodeVar().getType(), ssaVar.getCodeVar().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VarNode(MethodNode mth, int reg, int ssa, ArgType type, String name) {
|
||||||
|
this.mth = mth;
|
||||||
|
this.reg = reg;
|
||||||
|
this.ssa = ssa;
|
||||||
|
this.type = type;
|
||||||
|
this.name = name;
|
||||||
|
this.varRef = VarRef.fromVarNode(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getMth() {
|
||||||
|
return mth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReg() {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSsa() {
|
||||||
|
return ssa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArgType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VarRef getVarRef() {
|
||||||
|
return varRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefPosition() {
|
||||||
|
return defPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefPosition(int pos) {
|
||||||
|
this.defPos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.VAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int h = 31 * getReg() + getSsa();
|
||||||
|
return 31 * h + mth.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof VarNode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VarNode other = (VarNode) o;
|
||||||
|
return getReg() == other.getReg()
|
||||||
|
&& getSsa() == other.getSsa()
|
||||||
|
&& getMth().equals(other.getMth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarNode{r" + reg + 'v' + ssa + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package jadx.api.metadata.annotations;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable reference by position of VarNode in code metadata.
|
||||||
|
* <br>
|
||||||
|
* Because on creation position not yet known,
|
||||||
|
* VarRef created using VarNode as a source of ref pos during serialization.
|
||||||
|
* <br>
|
||||||
|
* On metadata deserialization created with ref pos directly.
|
||||||
|
*/
|
||||||
|
public abstract class VarRef implements ICodeAnnotation {
|
||||||
|
|
||||||
|
public static VarRef fromPos(int refPos) {
|
||||||
|
if (refPos == 0) {
|
||||||
|
throw new IllegalArgumentException("Zero refPos");
|
||||||
|
}
|
||||||
|
return new FixedVarRef(refPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VarRef fromVarNode(VarNode varNode) {
|
||||||
|
return new RelatedVarRef(varNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getRefPos();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.VAR_REF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class FixedVarRef extends VarRef {
|
||||||
|
private final int refPos;
|
||||||
|
|
||||||
|
public FixedVarRef(int refPos) {
|
||||||
|
this.refPos = refPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRefPos() {
|
||||||
|
return refPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class RelatedVarRef extends VarRef {
|
||||||
|
private final VarNode varNode;
|
||||||
|
|
||||||
|
public RelatedVarRef(VarNode varNode) {
|
||||||
|
this.varNode = varNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRefPos() {
|
||||||
|
return varNode.getDefPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarRef{" + varNode + ", name=" + varNode.getName() + ", mth=" + varNode.getMth() + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VarRef{" + getRefPos() + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package jadx.api.metadata.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NavigableMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
|
public class CodeMetadataStorage implements ICodeMetadata {
|
||||||
|
|
||||||
|
public static ICodeMetadata build(Map<Integer, Integer> lines, Map<Integer, ICodeAnnotation> map) {
|
||||||
|
if (map.isEmpty() && lines.isEmpty()) {
|
||||||
|
return ICodeMetadata.EMPTY;
|
||||||
|
}
|
||||||
|
Comparator<Integer> reverseCmp = Comparator.comparingInt(Integer::intValue).reversed();
|
||||||
|
NavigableMap<Integer, ICodeAnnotation> navMap = new TreeMap<>(reverseCmp);
|
||||||
|
navMap.putAll(map);
|
||||||
|
return new CodeMetadataStorage(lines, navMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ICodeMetadata empty() {
|
||||||
|
return new CodeMetadataStorage(Collections.emptyMap(), Collections.emptyNavigableMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<Integer, Integer> lines;
|
||||||
|
|
||||||
|
private final NavigableMap<Integer, ICodeAnnotation> navMap;
|
||||||
|
|
||||||
|
private CodeMetadataStorage(Map<Integer, Integer> lines, NavigableMap<Integer, ICodeAnnotation> navMap) {
|
||||||
|
this.lines = lines;
|
||||||
|
this.navMap = navMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeAnnotation getAt(int position) {
|
||||||
|
return navMap.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation getClosestUp(int position) {
|
||||||
|
Map.Entry<Integer, ICodeAnnotation> entryBefore = navMap.higherEntry(position);
|
||||||
|
return entryBefore != null ? entryBefore.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation searchUp(int position, ICodeAnnotation.AnnType annType) {
|
||||||
|
for (ICodeAnnotation v : navMap.tailMap(position, true).values()) {
|
||||||
|
if (v.getAnnType() == annType) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ICodeAnnotation searchUp(int position, int limitPos, ICodeAnnotation.AnnType annType) {
|
||||||
|
for (ICodeAnnotation v : navMap.subMap(position, true, limitPos, true).values()) {
|
||||||
|
if (v.getAnnType() == annType) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T searchUp(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : navMap.tailMap(startPos, true).entrySet()) {
|
||||||
|
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T searchDown(int startPos, BiFunction<Integer, ICodeAnnotation, T> visitor) {
|
||||||
|
NavigableMap<Integer, ICodeAnnotation> map = navMap.headMap(startPos, true).descendingMap();
|
||||||
|
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||||
|
T value = visitor.apply(entry.getKey(), entry.getValue());
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeNodeRef getNodeAt(int position) {
|
||||||
|
return navMap.tailMap(position, true)
|
||||||
|
.values().stream()
|
||||||
|
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICodeNodeRef getNodeBelow(int position) {
|
||||||
|
return navMap.headMap(position, true).descendingMap()
|
||||||
|
.values().stream()
|
||||||
|
.flatMap(CodeMetadataStorage::mapEnclosingNode)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<ICodeNodeRef> mapEnclosingNode(ICodeAnnotation ann) {
|
||||||
|
if (ann instanceof NodeDeclareRef) {
|
||||||
|
ICodeNodeRef node = ((NodeDeclareRef) ann).getNode();
|
||||||
|
if (node instanceof ClassNode || node instanceof MethodNode) {
|
||||||
|
return Stream.of(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NavigableMap<Integer, ICodeAnnotation> getAsMap() {
|
||||||
|
return navMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Integer, Integer> getLineMapping() {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CodeMetadata{lines=" + lines
|
||||||
|
+ ", annotations=\n" + Utils.listToString(navMap.entrySet(), "\n") + "\n}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package jadx.api.utils;
|
||||||
|
|
||||||
|
import jadx.api.ICodeWriter;
|
||||||
|
|
||||||
|
public class CodeUtils {
|
||||||
|
|
||||||
|
public static String getLineForPos(String code, int pos) {
|
||||||
|
int start = getLineStartForPos(code, pos);
|
||||||
|
int end = getLineEndForPos(code, pos);
|
||||||
|
return code.substring(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineStartForPos(String code, int pos) {
|
||||||
|
String newLine = ICodeWriter.NL;
|
||||||
|
int start = code.lastIndexOf(newLine, pos);
|
||||||
|
return start == -1 ? 0 : start + newLine.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineEndForPos(String code, int pos) {
|
||||||
|
int end = code.indexOf(ICodeWriter.NL, pos);
|
||||||
|
return end == -1 ? code.length() : end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLineNumForPos(String code, int pos) {
|
||||||
|
String newLine = ICodeWriter.NL;
|
||||||
|
int newLineLen = newLine.length();
|
||||||
|
int line = 1;
|
||||||
|
int prev = 0;
|
||||||
|
while (true) {
|
||||||
|
int next = code.indexOf(newLine, prev);
|
||||||
|
if (next >= pos) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
prev = next + newLineLen;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,10 @@ public class ProcessClass {
|
|||||||
return generateCode(topParentClass);
|
return generateCode(topParentClass);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
process(cls, false);
|
||||||
|
return ICodeInfo.EMPTY;
|
||||||
|
}
|
||||||
for (ClassNode depCls : cls.getDependencies()) {
|
for (ClassNode depCls : cls.getDependencies()) {
|
||||||
process(depCls, false);
|
process(depCls, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.data.annotations.VarRef;
|
|
||||||
import jadx.api.plugins.input.data.MethodHandleType;
|
import jadx.api.plugins.input.data.MethodHandleType;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -109,7 +108,7 @@ public class InsnGen {
|
|||||||
if (arg.isRegister()) {
|
if (arg.isRegister()) {
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachAnnotation(VarRef.get(mth, reg));
|
code.attachAnnotation(VarNode.getRef(mth, reg));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().useArg(reg));
|
code.add(mgen.getNameGen().useArg(reg));
|
||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
@@ -162,8 +161,15 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
useType(code, codeVar.getType());
|
useType(code, codeVar.getType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
|
defVar(code, codeVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable definition without type, only var name
|
||||||
|
*/
|
||||||
|
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||||
}
|
}
|
||||||
@@ -939,7 +945,7 @@ public class InsnGen {
|
|||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
|
||||||
code.add(nameGen.assignArg(argCodeVar));
|
defVar(code, argCodeVar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// force set external arg names into call method args
|
// force set external arg names into call method args
|
||||||
@@ -947,7 +953,8 @@ public class InsnGen {
|
|||||||
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
int startArg = customNode.getHandleType() == MethodHandleType.INVOKE_STATIC ? 0 : 1; // skip 'this' arg
|
||||||
for (int i = startArg; i < extArgsCount; i++) {
|
for (int i = startArg; i < extArgsCount; i++) {
|
||||||
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
|
||||||
callArgs.get(i).setName(extArg.getName());
|
RegisterArg callRegArg = callArgs.get(i);
|
||||||
|
callRegArg.getSVar().setCodeVar(extArg.getSVar().getCodeVar());
|
||||||
}
|
}
|
||||||
code.add(" -> {");
|
code.add(" -> {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.AccessFlags;
|
import jadx.api.plugins.input.data.AccessFlags;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
@@ -243,7 +243,7 @@ public class MethodGen {
|
|||||||
code.add(' ');
|
code.add(' ');
|
||||||
String varName = nameGen.assignArg(var);
|
String varName = nameGen.assignArg(var);
|
||||||
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
if (code.isMetadataSupported() && ssaVar != null /* for fallback mode */) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, var));
|
code.attachDefinition(VarNode.get(mth, var));
|
||||||
}
|
}
|
||||||
code.add(varName);
|
code.add(varName);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
@@ -173,7 +174,7 @@ public class NameGen {
|
|||||||
|
|
||||||
private String makeNameForType(ArgType type) {
|
private String makeNameForType(ArgType type) {
|
||||||
if (type.isPrimitive()) {
|
if (type.isPrimitive()) {
|
||||||
return makeNameForPrimitive(type);
|
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||||
}
|
}
|
||||||
if (type.isArray()) {
|
if (type.isArray()) {
|
||||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||||
@@ -181,10 +182,6 @@ public class NameGen {
|
|||||||
return makeNameForObject(type);
|
return makeNameForObject(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String makeNameForPrimitive(ArgType type) {
|
|
||||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String makeNameForObject(ArgType type) {
|
private String makeNameForObject(ArgType type) {
|
||||||
if (type.isGenericType()) {
|
if (type.isGenericType()) {
|
||||||
return StringUtils.escape(type.getObject().toLowerCase());
|
return StringUtils.escape(type.getObject().toLowerCase());
|
||||||
@@ -194,23 +191,32 @@ public class NameGen {
|
|||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
return makeNameForCheckedClass(ClassInfo.fromType(mth.root(), type));
|
||||||
String shortName = extClsInfo.getShortName();
|
|
||||||
String vName = fromName(shortName);
|
|
||||||
if (vName != null) {
|
|
||||||
return vName;
|
|
||||||
}
|
|
||||||
if (shortName != null) {
|
|
||||||
String lower = StringUtils.escape(shortName.toLowerCase());
|
|
||||||
if (shortName.equals(lower)) {
|
|
||||||
return lower + "Var";
|
|
||||||
}
|
|
||||||
return lower;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return StringUtils.escape(type.toString());
|
return StringUtils.escape(type.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String makeNameForCheckedClass(ClassInfo classInfo) {
|
||||||
|
String shortName = classInfo.getAliasShortName();
|
||||||
|
String vName = fromName(shortName);
|
||||||
|
if (vName != null) {
|
||||||
|
return vName;
|
||||||
|
}
|
||||||
|
String lower = StringUtils.escape(shortName.toLowerCase());
|
||||||
|
if (shortName.equals(lower)) {
|
||||||
|
return lower + "Var";
|
||||||
|
}
|
||||||
|
return lower;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeNameForClass(ClassInfo classInfo) {
|
||||||
|
String alias = getAliasForObject(classInfo.getFullName());
|
||||||
|
if (alias != null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
return makeNameForCheckedClass(classInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private static String fromName(String name) {
|
private static String fromName(String name) {
|
||||||
if (name == null || name.isEmpty()) {
|
if (name == null || name.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -241,7 +247,12 @@ public class NameGen {
|
|||||||
|
|
||||||
case CONSTRUCTOR:
|
case CONSTRUCTOR:
|
||||||
ConstructorInsn co = (ConstructorInsn) insn;
|
ConstructorInsn co = (ConstructorInsn) insn;
|
||||||
return makeNameForObject(co.getClassType().getType());
|
MethodNode callMth = mth.root().getMethodUtils().resolveMethod(co);
|
||||||
|
if (callMth != null && callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
|
// don't use name of anonymous class
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return makeNameForClass(co.getClassType());
|
||||||
|
|
||||||
case ARRAY_LENGTH:
|
case ARRAY_LENGTH:
|
||||||
return "length";
|
return "length";
|
||||||
@@ -267,11 +278,11 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||||
String name = callMth.getName();
|
String name = callMth.getAlias();
|
||||||
ArgType declType = callMth.getDeclClass().getType();
|
ClassInfo declClass = callMth.getDeclClass();
|
||||||
if ("getInstance".equals(name)) {
|
if ("getInstance".equals(name)) {
|
||||||
// e.g. Cipher.getInstance
|
// e.g. Cipher.getInstance
|
||||||
return makeNameForType(declType);
|
return makeNameForClass(declClass);
|
||||||
}
|
}
|
||||||
if (name.startsWith("get") || name.startsWith("set")) {
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
return fromName(name.substring(3));
|
return fromName(name.substring(3));
|
||||||
@@ -280,9 +291,9 @@ public class NameGen {
|
|||||||
return "it";
|
return "it";
|
||||||
}
|
}
|
||||||
if ("toString".equals(name)) {
|
if ("toString".equals(name)) {
|
||||||
return makeNameForType(declType);
|
return makeNameForClass(declClass);
|
||||||
}
|
}
|
||||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
if ("forName".equals(name) && declClass.getType().equals(ArgType.CLASS)) {
|
||||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||||
}
|
}
|
||||||
if (name.startsWith("to")) {
|
if (name.startsWith("to")) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
@@ -26,6 +26,7 @@ import jadx.core.dex.instructions.args.CodeVar;
|
|||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.NamedArg;
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.IBlock;
|
import jadx.core.dex.nodes.IBlock;
|
||||||
@@ -346,11 +347,11 @@ public class RegionGen extends InsnGen {
|
|||||||
if (arg == null) {
|
if (arg == null) {
|
||||||
code.add("unknown"); // throwing exception is too late at this point
|
code.add("unknown"); // throwing exception is too late at this point
|
||||||
} else if (arg instanceof RegisterArg) {
|
} else if (arg instanceof RegisterArg) {
|
||||||
CodeVar codeVar = ((RegisterArg) arg).getSVar().getCodeVar();
|
SSAVar ssaVar = ((RegisterArg) arg).getSVar();
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
code.attachDefinition(VarDeclareRef.get(mth, codeVar));
|
code.attachDefinition(VarNode.get(mth, ssaVar));
|
||||||
}
|
}
|
||||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
code.add(mgen.getNameGen().assignArg(ssaVar.getCodeVar()));
|
||||||
} else if (arg instanceof NamedArg) {
|
} else if (arg instanceof NamedArg) {
|
||||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import com.google.gson.FieldNamingPolicy;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
|
||||||
import jadx.api.impl.AnnotatedCodeWriter;
|
import jadx.api.impl.AnnotatedCodeWriter;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.core.codegen.ClassGen;
|
import jadx.core.codegen.ClassGen;
|
||||||
import jadx.core.codegen.MethodGen;
|
import jadx.core.codegen.MethodGen;
|
||||||
import jadx.core.codegen.json.cls.JsonClass;
|
import jadx.core.codegen.json.cls.JsonClass;
|
||||||
@@ -180,24 +180,27 @@ public class JsonCodeGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
ICodeMetadata metadata = code.getCodeMetadata();
|
||||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||||
|
|
||||||
int linesCount = lines.length;
|
int linesCount = lines.length;
|
||||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||||
|
int lineStartPos = 0;
|
||||||
|
int newLineLen = ICodeWriter.NL.length();
|
||||||
for (int i = 0; i < linesCount; i++) {
|
for (int i = 0; i < linesCount; i++) {
|
||||||
String codeLine = lines[i];
|
String codeLine = lines[i];
|
||||||
int line = i + 2;
|
int line = i + 2;
|
||||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||||
jsonCodeLine.setCode(codeLine);
|
jsonCodeLine.setCode(codeLine);
|
||||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||||
Object obj = annotations.get(new CodePosition(line));
|
Object obj = metadata.getAt(lineStartPos);
|
||||||
if (obj instanceof InsnCodeOffset) {
|
if (obj instanceof InsnCodeOffset) {
|
||||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||||
}
|
}
|
||||||
codeLines.add(jsonCodeLine);
|
codeLines.add(jsonCodeLine);
|
||||||
|
lineStartPos += codeLine.length() + newLineLen;
|
||||||
}
|
}
|
||||||
return codeLines;
|
return codeLines;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ public enum AFlag {
|
|||||||
RERUN_SSA_TRANSFORM,
|
RERUN_SSA_TRANSFORM,
|
||||||
|
|
||||||
METHOD_CANDIDATE_FOR_INLINE,
|
METHOD_CANDIDATE_FOR_INLINE,
|
||||||
|
USE_LINES_HINTS, // source lines info in methods can be trusted
|
||||||
|
|
||||||
DISABLE_BLOCKS_LOCK,
|
DISABLE_BLOCKS_LOCK,
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ public interface ILineAttributeNode {
|
|||||||
|
|
||||||
void setSourceLine(int sourceLine);
|
void setSourceLine(int sourceLine);
|
||||||
|
|
||||||
int getDecompiledLine();
|
|
||||||
|
|
||||||
void setDecompiledLine(int line);
|
|
||||||
|
|
||||||
int getDefPosition();
|
int getDefPosition();
|
||||||
|
|
||||||
void setDefPosition(int pos);
|
void setDefPosition(int pos);
|
||||||
|
|||||||
@@ -7,21 +7,11 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
|
|
||||||
private int sourceLine;
|
private int sourceLine;
|
||||||
|
|
||||||
private int decompiledLine;
|
/**
|
||||||
|
* Position where a node declared at in decompiled code
|
||||||
// the position exactly where a node declared at in decompiled java code.
|
*/
|
||||||
private int defPosition;
|
private int defPosition;
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDefPosition() {
|
|
||||||
return this.defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDefPosition(int defPosition) {
|
|
||||||
this.defPosition = defPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSourceLine() {
|
public int getSourceLine() {
|
||||||
return sourceLine;
|
return sourceLine;
|
||||||
@@ -33,13 +23,13 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDefPosition() {
|
||||||
return decompiledLine;
|
return this.defPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDecompiledLine(int decompiledLine) {
|
public void setDefPosition(int defPosition) {
|
||||||
this.decompiledLine = decompiledLine;
|
this.defPosition = defPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
public void addSourceLineFrom(LineAttrNode lineAttrNode) {
|
||||||
@@ -50,6 +40,6 @@ public abstract class LineAttrNode extends AttrNode implements ILineAttributeNod
|
|||||||
|
|
||||||
public void copyLines(LineAttrNode lineAttrNode) {
|
public void copyLines(LineAttrNode lineAttrNode) {
|
||||||
setSourceLine(lineAttrNode.getSourceLine());
|
setSourceLine(lineAttrNode.getSourceLine());
|
||||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
setDefPosition(lineAttrNode.getDefPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import org.jetbrains.annotations.TestOnly;
|
|||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
@@ -874,28 +874,37 @@ public abstract class ArgType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
|
||||||
if (!type.isObject() || type.isGenericType()) {
|
if (type.isGenericType()) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
if (type.isArray()) {
|
||||||
|
ArgType rootType = type.getArrayRootElement();
|
||||||
|
ArgType aliasType = tryToResolveClassAlias(root, rootType);
|
||||||
|
if (aliasType == rootType) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
return ArgType.array(aliasType, type.getArrayDimension());
|
||||||
|
}
|
||||||
|
if (type.isObject()) {
|
||||||
|
ArgType wildcardType = type.getWildcardType();
|
||||||
|
if (wildcardType != null) {
|
||||||
|
return new WildcardType(tryToResolveClassAlias(root, wildcardType), type.getWildcardBound());
|
||||||
|
}
|
||||||
|
ClassInfo clsInfo = ClassInfo.fromName(root, type.getObject());
|
||||||
|
ArgType baseType = clsInfo.hasAlias() ? ArgType.object(clsInfo.getAliasFullName()) : type;
|
||||||
|
if (!type.isGeneric()) {
|
||||||
|
return baseType;
|
||||||
|
}
|
||||||
|
List<ArgType> genericTypes = type.getGenericTypes();
|
||||||
|
if (genericTypes != null) {
|
||||||
|
return new GenericObject(baseType.getObject(), tryToResolveClassAlias(root, genericTypes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
ClassNode cls = root.resolveClass(type);
|
public static List<ArgType> tryToResolveClassAlias(RootNode root, List<ArgType> types) {
|
||||||
if (cls == null) {
|
return ListUtils.map(types, t -> tryToResolveClassAlias(root, t));
|
||||||
return type;
|
|
||||||
}
|
|
||||||
ClassInfo clsInfo = cls.getClassInfo();
|
|
||||||
if (!clsInfo.hasAlias()) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
String aliasFullName = clsInfo.getAliasFullName();
|
|
||||||
if (type.isGeneric()) {
|
|
||||||
if (type instanceof GenericObject) {
|
|
||||||
return new GenericObject(aliasFullName, type.getGenericTypes());
|
|
||||||
}
|
|
||||||
if (type instanceof WildcardType) {
|
|
||||||
return new WildcardType(ArgType.object(aliasFullName), type.getWildcardBound());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ArgType.object(aliasFullName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarRef;
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
|
||||||
public class CodeVar {
|
public class CodeVar {
|
||||||
private String name;
|
private String name;
|
||||||
@@ -15,7 +15,7 @@ public class CodeVar {
|
|||||||
private boolean isThis;
|
private boolean isThis;
|
||||||
private boolean isDeclared;
|
private boolean isDeclared;
|
||||||
|
|
||||||
private VarRef cachedVarRef; // set and used at codegen stage
|
private VarNode cachedVarNode; // set and used at codegen stage
|
||||||
|
|
||||||
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
|
||||||
CodeVar var = new CodeVar();
|
CodeVar var = new CodeVar();
|
||||||
@@ -94,12 +94,12 @@ public class CodeVar {
|
|||||||
isDeclared = declared;
|
isDeclared = declared;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VarRef getCachedVarRef() {
|
public VarNode getCachedVarNode() {
|
||||||
return cachedVarRef;
|
return cachedVarNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCachedVarRef(VarRef cachedVarRef) {
|
public void setCachedVarNode(VarNode varNode) {
|
||||||
this.cachedVarRef = cachedVarRef;
|
this.cachedVarNode = varNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
String clsRawName = getRawName();
|
String clsRawName = getRawName();
|
||||||
if (searchInCache) {
|
if (searchInCache) {
|
||||||
ICodeInfo code = codeCache.get(clsRawName);
|
ICodeInfo code = codeCache.get(clsRawName);
|
||||||
if (code != null && code != ICodeInfo.EMPTY) {
|
if (code != ICodeInfo.EMPTY) {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -817,6 +817,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
|||||||
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
return clsData == null ? "synthetic" : clsData.getInputFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return clsInfo.hashCode();
|
return clsInfo.hashCode();
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
|||||||
return parentClass.root();
|
return parentClass.root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.FIELD;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return fieldInfo.hashCode();
|
return fieldInfo.hashCode();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package jadx.core.dex.nodes;
|
package jadx.core.dex.nodes;
|
||||||
|
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
|
|
||||||
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
|
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode, ICodeNodeRef {
|
||||||
AccessInfo getAccessFlags();
|
AccessInfo getAccessFlags();
|
||||||
|
|
||||||
void setAccessFlags(AccessInfo newAccessFlags);
|
void setAccessFlags(AccessInfo newAccessFlags);
|
||||||
|
|||||||
@@ -592,6 +592,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnType getAnnType() {
|
||||||
|
return AnnType.METHOD;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return mthInfo.hashCode();
|
return mthInfo.hashCode();
|
||||||
|
|||||||
@@ -45,6 +45,15 @@ public class MethodUtils {
|
|||||||
return root.getClsp().getMethodDetails(callMth);
|
return root.getClsp().getMethodDetails(callMth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MethodNode resolveMethod(BaseInvokeNode invokeNode) {
|
||||||
|
IMethodDetails methodDetails = getMethodDetails(invokeNode);
|
||||||
|
if (methodDetails instanceof MethodNode) {
|
||||||
|
return ((MethodNode) methodDetails);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
|
* Search methods with same name and args count in class hierarchy starting from {@code startCls}
|
||||||
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
|
* Beware {@code startCls} can be different from {@code mthInfo.getDeclClass()}
|
||||||
|
|||||||
@@ -278,6 +278,16 @@ public final class IfCondition extends AttrNode {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSourceLine() {
|
||||||
|
for (InsnNode insn : collectInsns()) {
|
||||||
|
int line = insn.getSourceLine();
|
||||||
|
if (line != 0) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public InsnNode getFirstInsn() {
|
public InsnNode getFirstInsn() {
|
||||||
if (mode == Mode.COMPARE) {
|
if (mode == Mode.COMPARE) {
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ public class MoveInlineVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
SSAVar ssaVar = resultArg.getSVar();
|
SSAVar ssaVar = resultArg.getSVar();
|
||||||
if (ssaVar.isUsedInPhi()) {
|
if (ssaVar.isUsedInPhi()) {
|
||||||
return deleteMove(mth, move);
|
return false;
|
||||||
|
// TODO: review conditions of 'up' move inline (test TestMoveInline)
|
||||||
|
// return deleteMove(mth, move);
|
||||||
}
|
}
|
||||||
RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO);
|
RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO);
|
||||||
for (RegisterArg useArg : ssaVar.getUseList()) {
|
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||||
|
|||||||
+18
-1
@@ -1,12 +1,15 @@
|
|||||||
package jadx.core.dex.visitors.debuginfo;
|
package jadx.core.dex.visitors.debuginfo;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.IDebugInfo;
|
import jadx.api.plugins.input.data.IDebugInfo;
|
||||||
import jadx.api.plugins.input.data.ILocalVar;
|
import jadx.api.plugins.input.data.ILocalVar;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||||
|
import jadx.core.dex.instructions.InsnType;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
@@ -17,6 +20,7 @@ import jadx.core.dex.visitors.AbstractVisitor;
|
|||||||
import jadx.core.dex.visitors.JadxVisitor;
|
import jadx.core.dex.visitors.JadxVisitor;
|
||||||
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
import jadx.core.dex.visitors.blocks.BlockSplitter;
|
||||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
|
||||||
@JadxVisitor(
|
@JadxVisitor(
|
||||||
@@ -52,17 +56,30 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
|
|||||||
if (lineMapping.isEmpty()) {
|
if (lineMapping.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Map<Integer, Integer> linesStat = new HashMap<>(); // count repeating lines
|
||||||
for (Map.Entry<Integer, Integer> entry : lineMapping.entrySet()) {
|
for (Map.Entry<Integer, Integer> entry : lineMapping.entrySet()) {
|
||||||
try {
|
try {
|
||||||
Integer offset = entry.getKey();
|
Integer offset = entry.getKey();
|
||||||
InsnNode insn = insnArr[offset];
|
InsnNode insn = insnArr[offset];
|
||||||
if (insn != null) {
|
if (insn != null) {
|
||||||
insn.setSourceLine(entry.getValue());
|
int line = entry.getValue();
|
||||||
|
insn.setSourceLine(line);
|
||||||
|
if (insn.getType() != InsnType.NOP) {
|
||||||
|
linesStat.merge(line, 1, (v, one) -> v + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mth.addWarnComment("Error attach source line", e);
|
mth.addWarnComment("Error attach source line", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 3 here is allowed maximum for lines repeat,
|
||||||
|
// can occur in indexed 'for' loops (3 instructions with same line)
|
||||||
|
List<Map.Entry<Integer, Integer>> repeatingLines = ListUtils.filter(linesStat.entrySet(), p -> p.getValue() > 3);
|
||||||
|
if (repeatingLines.isEmpty()) {
|
||||||
|
mth.add(AFlag.USE_LINES_HINTS);
|
||||||
|
} else {
|
||||||
|
mth.addDebugComment("Don't trust debug lines info. Repeating lines: " + repeatingLines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachDebugInfo(MethodNode mth, List<ILocalVar> localVars, InsnNode[] insnArr) {
|
private void attachDebugInfo(MethodNode mth, List<ILocalVar> localVars, InsnNode[] insnArr) {
|
||||||
|
|||||||
@@ -38,15 +38,105 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||||
if (region instanceof IfRegion) {
|
if (region instanceof IfRegion) {
|
||||||
IfRegion ifRegion = (IfRegion) region;
|
IfRegion ifRegion = (IfRegion) region;
|
||||||
simplifyIfCondition(ifRegion);
|
orderBranches(mth, ifRegion);
|
||||||
moveReturnToThenBlock(mth, ifRegion);
|
markElseIfChains(mth, ifRegion);
|
||||||
moveBreakToThenBlock(ifRegion);
|
|
||||||
markElseIfChains(ifRegion);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||||
|
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||||
|
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (RegionUtils.isEmpty(ifRegion.getThenRegion())) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mth.contains(AFlag.USE_LINES_HINTS)) {
|
||||||
|
int thenLine = RegionUtils.getFirstSourceLine(ifRegion.getThenRegion());
|
||||||
|
int elseLine = RegionUtils.getFirstSourceLine(ifRegion.getElseRegion());
|
||||||
|
if (thenLine != 0 && elseLine != 0) {
|
||||||
|
if (thenLine > elseLine) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ifRegion.simplifyCondition()) {
|
||||||
|
IfCondition condition = ifRegion.getCondition();
|
||||||
|
if (condition != null && condition.getMode() == Mode.NOT) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int thenSize = insnsCount(ifRegion.getThenRegion());
|
||||||
|
int elseSize = insnsCount(ifRegion.getElseRegion());
|
||||||
|
if (isSimpleExitBlock(mth, ifRegion.getElseRegion())) {
|
||||||
|
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
|
||||||
|
if (elseSize < thenSize) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
|
||||||
|
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
||||||
|
// single return at method end will be removed later
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!lastRegion) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean thenExit = RegionUtils.hasExitBlock(ifRegion.getThenRegion());
|
||||||
|
boolean elseExit = RegionUtils.hasExitBlock(ifRegion.getElseRegion());
|
||||||
|
if (elseExit && (!thenExit || elseSize < thenSize)) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// move 'if' from 'then' branch to make 'else if' chain
|
||||||
|
if (isIfRegion(ifRegion.getThenRegion())
|
||||||
|
&& !isIfRegion(ifRegion.getElseRegion())
|
||||||
|
&& !thenExit) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// move 'break' into 'then' branch
|
||||||
|
if (RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
|
||||||
|
invertIfRegion(ifRegion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isIfRegion(IContainer container) {
|
||||||
|
if (container instanceof IfRegion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (container instanceof IRegion) {
|
||||||
|
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||||
|
return subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark if-else-if chains
|
||||||
|
*/
|
||||||
|
private static void markElseIfChains(MethodNode mth, IfRegion ifRegion) {
|
||||||
|
if (isSimpleExitBlock(mth, ifRegion.getThenRegion())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IContainer elsRegion = ifRegion.getElseRegion();
|
||||||
|
if (elsRegion instanceof Region) {
|
||||||
|
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
|
||||||
|
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||||
|
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
|
||||||
|
elsRegion.add(AFlag.ELSE_IF_CHAIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class RemoveRedundantElseVisitor implements IRegionIterativeVisitor {
|
private static class RemoveRedundantElseVisitor implements IRegionIterativeVisitor {
|
||||||
@Override
|
@Override
|
||||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||||
@@ -57,76 +147,6 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void simplifyIfCondition(IfRegion ifRegion) {
|
|
||||||
if (ifRegion.simplifyCondition()) {
|
|
||||||
IfCondition condition = ifRegion.getCondition();
|
|
||||||
if (condition.getMode() == Mode.NOT) {
|
|
||||||
invertIfRegion(ifRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IContainer elseRegion = ifRegion.getElseRegion();
|
|
||||||
if (elseRegion == null || RegionUtils.isEmpty(elseRegion)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean thenIsEmpty = RegionUtils.isEmpty(ifRegion.getThenRegion());
|
|
||||||
if (thenIsEmpty || hasSimpleReturnBlock(ifRegion.getThenRegion())) {
|
|
||||||
invertIfRegion(ifRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!thenIsEmpty) {
|
|
||||||
// move 'if' from then to make 'else if' chain
|
|
||||||
if (isIfRegion(ifRegion.getThenRegion())
|
|
||||||
&& !isIfRegion(elseRegion)) {
|
|
||||||
invertIfRegion(ifRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isIfRegion(IContainer container) {
|
|
||||||
if (container instanceof IfRegion) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (container instanceof IRegion) {
|
|
||||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
|
||||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
|
|
||||||
if (!mth.isVoidReturn()
|
|
||||||
&& hasSimpleReturnBlock(ifRegion.getElseRegion())
|
|
||||||
/* && insnsCount(ifRegion.getThenRegion()) < 2 */) {
|
|
||||||
invertIfRegion(ifRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void moveBreakToThenBlock(IfRegion ifRegion) {
|
|
||||||
if (ifRegion.getElseRegion() != null
|
|
||||||
&& RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
|
|
||||||
invertIfRegion(ifRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark if-else-if chains
|
|
||||||
*/
|
|
||||||
private static void markElseIfChains(IfRegion ifRegion) {
|
|
||||||
if (hasSimpleReturnBlock(ifRegion.getThenRegion())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IContainer elsRegion = ifRegion.getElseRegion();
|
|
||||||
if (elsRegion instanceof Region) {
|
|
||||||
List<IContainer> subBlocks = ((Region) elsRegion).getSubBlocks();
|
|
||||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
|
||||||
subBlocks.get(0).add(AFlag.ELSE_IF_CHAIN);
|
|
||||||
elsRegion.add(AFlag.ELSE_IF_CHAIN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
||||||
if (ifRegion.getElseRegion() == null
|
if (ifRegion.getElseRegion() == null
|
||||||
|| ifRegion.contains(AFlag.ELSE_IF_CHAIN)
|
|| ifRegion.contains(AFlag.ELSE_IF_CHAIN)
|
||||||
@@ -162,16 +182,16 @@ public class IfRegionVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasSimpleReturnBlock(IContainer region) {
|
private static boolean isSimpleExitBlock(MethodNode mth, IContainer container) {
|
||||||
if (region == null) {
|
if (container == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (region.contains(AFlag.RETURN)) {
|
if (container.contains(AFlag.RETURN) || RegionUtils.isExitBlock(mth, container)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (region instanceof IRegion) {
|
if (container instanceof IRegion) {
|
||||||
List<IContainer> subBlocks = ((IRegion) region).getSubBlocks();
|
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||||
return subBlocks.size() == 1 && subBlocks.get(0).contains(AFlag.RETURN);
|
return subBlocks.size() == 1 && RegionUtils.isExitBlock(mth, subBlocks.get(0));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||||
|
|
||||||
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||||
|
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||||
// sometimes source lines incorrect
|
// sometimes source lines incorrect
|
||||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||||
@@ -134,6 +135,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
|||||||
InsnArg thenArg = InsnArg.wrapInsnIntoArg(thenInsn);
|
InsnArg thenArg = InsnArg.wrapInsnIntoArg(thenInsn);
|
||||||
InsnArg elseArg = InsnArg.wrapInsnIntoArg(elseInsn);
|
InsnArg elseArg = InsnArg.wrapInsnIntoArg(elseInsn);
|
||||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), resArg, thenArg, elseArg);
|
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), resArg, thenArg, elseArg);
|
||||||
|
int branchLine = Math.max(thenInsn.getSourceLine(), elseInsn.getSourceLine());
|
||||||
|
ternInsn.setSourceLine(Math.max(ifRegion.getSourceLine(), branchLine));
|
||||||
|
|
||||||
InsnRemover.unbindResult(mth, elseInsn);
|
InsnRemover.unbindResult(mth, elseInsn);
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,16 @@ public class BlockUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getFirstSourceLine(IBlock block) {
|
||||||
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
|
int line = insn.getSourceLine();
|
||||||
|
if (line != 0) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static InsnNode getFirstInsn(@Nullable IBlock block) {
|
public static InsnNode getFirstInsn(@Nullable IBlock block) {
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
@@ -207,11 +217,22 @@ public class BlockUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isExitBlock(MethodNode mth, BlockNode block) {
|
public static boolean isExitBlock(MethodNode mth, BlockNode block) {
|
||||||
BlockNode exitBlock = mth.getExitBlock();
|
if (block == mth.getExitBlock()) {
|
||||||
if (block == exitBlock) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return exitBlock.getPredecessors().contains(block);
|
return isExitBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isExitBlock(BlockNode block) {
|
||||||
|
List<BlockNode> successors = block.getSuccessors();
|
||||||
|
if (successors.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (successors.size() == 1) {
|
||||||
|
BlockNode next = successors.get(0);
|
||||||
|
return next.getSuccessors().isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean containsExitInsn(IBlock block) {
|
public static boolean containsExitInsn(IBlock block) {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
@@ -95,7 +95,7 @@ public class CodeGenUtils {
|
|||||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
String indent = "";
|
String indent = "";
|
||||||
Object lineAnn = null;
|
ICodeAnnotation lineAnn = null;
|
||||||
for (String comment : comments) {
|
for (String comment : comments) {
|
||||||
for (String line : comment.split("\n")) {
|
for (String line : comment.split("\n")) {
|
||||||
if (first) {
|
if (first) {
|
||||||
@@ -104,7 +104,7 @@ public class CodeGenUtils {
|
|||||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
||||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
||||||
if (code.isMetadataSupported()) {
|
if (code.isMetadataSupported()) {
|
||||||
lineAnn = code.getRawAnnotations().get(new CodePosition(code.getLine()));
|
lineAnn = code.getRawAnnotations().get(startLinePos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
code.newLine().add(indent);
|
code.newLine().add(indent);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import jadx.core.dex.instructions.InsnType;
|
|||||||
import jadx.core.dex.nodes.BlockNode;
|
import jadx.core.dex.nodes.BlockNode;
|
||||||
import jadx.core.dex.nodes.IBlock;
|
import jadx.core.dex.nodes.IBlock;
|
||||||
import jadx.core.dex.nodes.IBranchRegion;
|
import jadx.core.dex.nodes.IBranchRegion;
|
||||||
|
import jadx.core.dex.nodes.IConditionRegion;
|
||||||
import jadx.core.dex.nodes.IContainer;
|
import jadx.core.dex.nodes.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
@@ -52,6 +53,7 @@ public class RegionUtils {
|
|||||||
throw new JadxRuntimeException(unknownContainerType(container));
|
throw new JadxRuntimeException(unknownContainerType(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static InsnNode getFirstInsn(IContainer container) {
|
public static InsnNode getFirstInsn(IContainer container) {
|
||||||
if (container instanceof IBlock) {
|
if (container instanceof IBlock) {
|
||||||
IBlock block = (IBlock) container;
|
IBlock block = (IBlock) container;
|
||||||
@@ -74,6 +76,37 @@ public class RegionUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getFirstSourceLine(IContainer container) {
|
||||||
|
if (container instanceof IBlock) {
|
||||||
|
return BlockUtils.getFirstSourceLine((IBlock) container);
|
||||||
|
}
|
||||||
|
if (container instanceof IConditionRegion) {
|
||||||
|
return ((IConditionRegion) container).getConditionSourceLine();
|
||||||
|
}
|
||||||
|
if (container instanceof IBranchRegion) {
|
||||||
|
IBranchRegion branchRegion = (IBranchRegion) container;
|
||||||
|
return getFirstSourceLine(branchRegion.getBranches());
|
||||||
|
}
|
||||||
|
if (container instanceof IRegion) {
|
||||||
|
IRegion region = (IRegion) container;
|
||||||
|
return getFirstSourceLine(region.getSubBlocks());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getFirstSourceLine(List<IContainer> containers) {
|
||||||
|
if (containers.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (IContainer container : containers) {
|
||||||
|
int line = getFirstSourceLine(container);
|
||||||
|
if (line != 0) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static InsnNode getLastInsn(IContainer container) {
|
public static InsnNode getLastInsn(IContainer container) {
|
||||||
if (container instanceof IBlock) {
|
if (container instanceof IBlock) {
|
||||||
IBlock block = (IBlock) container;
|
IBlock block = (IBlock) container;
|
||||||
@@ -112,31 +145,58 @@ 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);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if last block in region has no successors or jump out insn (return or break)
|
* Return true if last block in region has no successors or jump out insn (return or break)
|
||||||
*/
|
*/
|
||||||
public static boolean hasExitBlock(IContainer container) {
|
public static boolean hasExitBlock(IContainer container) {
|
||||||
|
if (container == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return hasExitBlock(container, container);
|
return hasExitBlock(container, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasExitBlock(IContainer rootContainer, IContainer container) {
|
private static boolean hasExitBlock(IContainer rootContainer, IContainer container) {
|
||||||
if (container instanceof BlockNode) {
|
if (container instanceof BlockNode) {
|
||||||
BlockNode blockNode = (BlockNode) container;
|
BlockNode blockNode = (BlockNode) container;
|
||||||
if (blockNode.getSuccessors().isEmpty()) {
|
if (BlockUtils.isExitBlock(blockNode)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return isInsnExitContainer(rootContainer, (IBlock) container);
|
return isInsnExitContainer(rootContainer, (IBlock) container);
|
||||||
} else if (container instanceof IBranchRegion) {
|
}
|
||||||
return false;
|
if (container instanceof IBranchRegion) {
|
||||||
} else if (container instanceof IBlock) {
|
IBranchRegion branchRegion = (IBranchRegion) container;
|
||||||
|
return ListUtils.allMatch(branchRegion.getBranches(), RegionUtils::hasExitBlock);
|
||||||
|
}
|
||||||
|
if (container instanceof IBlock) {
|
||||||
return isInsnExitContainer(rootContainer, (IBlock) container);
|
return isInsnExitContainer(rootContainer, (IBlock) container);
|
||||||
} else if (container instanceof IRegion) {
|
}
|
||||||
|
if (container instanceof IRegion) {
|
||||||
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
|
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
|
||||||
return !blocks.isEmpty()
|
return !blocks.isEmpty()
|
||||||
&& hasExitBlock(rootContainer, blocks.get(blocks.size() - 1));
|
&& hasExitBlock(rootContainer, blocks.get(blocks.size() - 1));
|
||||||
} else {
|
|
||||||
throw new JadxRuntimeException(unknownContainerType(container));
|
|
||||||
}
|
}
|
||||||
|
throw new JadxRuntimeException(unknownContainerType(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isInsnExitContainer(IContainer rootContainer, IBlock block) {
|
private static boolean isInsnExitContainer(IContainer rootContainer, IBlock block) {
|
||||||
@@ -192,17 +252,25 @@ public class RegionUtils {
|
|||||||
|
|
||||||
public static int insnsCount(IContainer container) {
|
public static int insnsCount(IContainer container) {
|
||||||
if (container instanceof IBlock) {
|
if (container instanceof IBlock) {
|
||||||
return ((IBlock) container).getInstructions().size();
|
List<InsnNode> insnList = ((IBlock) container).getInstructions();
|
||||||
} else if (container instanceof IRegion) {
|
int count = 0;
|
||||||
|
for (InsnNode insn : insnList) {
|
||||||
|
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
if (container instanceof IRegion) {
|
||||||
IRegion region = (IRegion) container;
|
IRegion region = (IRegion) container;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (IContainer block : region.getSubBlocks()) {
|
for (IContainer block : region.getSubBlocks()) {
|
||||||
count += insnsCount(block);
|
count += insnsCount(block);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
} else {
|
|
||||||
throw new JadxRuntimeException(unknownContainerType(container));
|
|
||||||
}
|
}
|
||||||
|
throw new JadxRuntimeException(unknownContainerType(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEmpty(IContainer container) {
|
public static boolean isEmpty(IContainer container) {
|
||||||
|
|||||||
@@ -8,16 +8,19 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitOption;
|
import java.nio.file.FileVisitOption;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -110,6 +113,12 @@ public class FileUtils {
|
|||||||
return dir.delete();
|
return dir.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void deleteDirIfExists(Path dir) {
|
||||||
|
if (Files.exists(dir)) {
|
||||||
|
deleteDir(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void deleteDir(Path dir) {
|
public static void deleteDir(Path dir) {
|
||||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||||
pathStream.sorted(Comparator.reverseOrder())
|
pathStream.sorted(Comparator.reverseOrder())
|
||||||
@@ -230,18 +239,19 @@ public class FileUtils {
|
|||||||
return new File(file.getParentFile(), name);
|
return new File(file.getParentFile(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String bytesToHex(byte[] bytes) {
|
private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII);
|
||||||
char[] hexArray = "0123456789abcdef".toCharArray();
|
|
||||||
if (bytes == null || bytes.length <= 0) {
|
public static String bytesToHex(byte[] bytes) {
|
||||||
return null;
|
if (bytes == null || bytes.length == 0) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
char[] hexChars = new char[bytes.length * 2];
|
byte[] hexChars = new byte[bytes.length * 2];
|
||||||
for (int j = 0; j < bytes.length; j++) {
|
for (int j = 0; j < bytes.length; j++) {
|
||||||
int v = bytes[j] & 0xFF;
|
int v = bytes[j] & 0xFF;
|
||||||
hexChars[j * 2] = hexArray[v >>> 4];
|
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||||
}
|
}
|
||||||
return new String(hexChars);
|
return new String(hexChars, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isZipFile(File file) {
|
public static boolean isZipFile(File file) {
|
||||||
@@ -275,4 +285,30 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
return new File(path);
|
return new File(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Path> toPaths(List<File> files) {
|
||||||
|
return files.stream().map(File::toPath).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Path> toPaths(File[] files) {
|
||||||
|
return Stream.of(files).map(File::toPath).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Path> fileNamesToPaths(List<String> fileNames) {
|
||||||
|
return fileNames.stream().map(Paths::get).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<File> toFiles(List<Path> paths) {
|
||||||
|
return paths.stream().map(Path::toFile).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5Sum(byte[] data) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(data);
|
||||||
|
return bytesToHex(md.digest());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Failed to build hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class JadxArgsValidatorOutDirsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkOutDirs(String outDir, String srcDir, String resDir) {
|
private void checkOutDirs(String outDir, String srcDir, String resDir) {
|
||||||
JadxArgsValidator.validate(args);
|
JadxArgsValidator.validate(new JadxDecompiler(args));
|
||||||
LOG.debug("Got dirs: out={}, src={}, res={}", args.getOutDir(), args.getOutDirSrc(), args.getOutDirRes());
|
LOG.debug("Got dirs: out={}, src={}, res={}", args.getOutDir(), args.getOutDirSrc(), args.getOutDirRes());
|
||||||
assertThat(args.getOutDir(), is(toFile(outDir)));
|
assertThat(args.getOutDir(), is(toFile(outDir)));
|
||||||
assertThat(args.getOutDirSrc(), is(toFile(srcDir)));
|
assertThat(args.getOutDirSrc(), is(toFile(srcDir)));
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.core.utils.files.FileUtils;
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
import jadx.plugins.input.dex.DexInputPlugin;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
@@ -38,6 +42,20 @@ public class JadxDecompilerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDirectDexInput() throws IOException {
|
||||||
|
try (JadxDecompiler jadx = new JadxDecompiler();
|
||||||
|
InputStream in = new FileInputStream(getFileFromSampleDir("hello.dex"))) {
|
||||||
|
jadx.addCustomLoad(new DexInputPlugin().loadDexFromInputStream(in, "input"));
|
||||||
|
jadx.load();
|
||||||
|
for (JavaClass cls : jadx.getClasses()) {
|
||||||
|
System.out.println(cls.getCode());
|
||||||
|
}
|
||||||
|
assertThat(jadx.getClasses(), Matchers.hasSize(1));
|
||||||
|
assertThat(jadx.getErrorsCount(), Matchers.is(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TEST_SAMPLES_DIR = "test-samples/";
|
private static final String TEST_SAMPLES_DIR = "test-samples/";
|
||||||
|
|
||||||
public static File getFileFromSampleDir(String fileName) {
|
public static File getFileFromSampleDir(String fileName) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
@@ -41,7 +40,8 @@ import jadx.api.JadxDecompiler;
|
|||||||
import jadx.api.JadxInternalAccess;
|
import jadx.api.JadxInternalAccess;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.args.DeobfuscationMapFileMode;
|
import jadx.api.args.DeobfuscationMapFileMode;
|
||||||
import jadx.api.data.annotations.InsnCodeOffset;
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
import jadx.core.dex.attributes.IAttributeNode;
|
||||||
@@ -150,9 +150,9 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
close(decompiledCompiler);
|
close(decompiledCompiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close(Closeable cloaseble) throws IOException {
|
private void close(Closeable closeable) throws IOException {
|
||||||
if (cloaseble != null) {
|
if (closeable != null) {
|
||||||
cloaseble.close();
|
closeable.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
private void printCodeWithLineNumbers(ICodeInfo code) {
|
private void printCodeWithLineNumbers(ICodeInfo code) {
|
||||||
String codeStr = code.getCodeStr();
|
String codeStr = code.getCodeStr();
|
||||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
|
||||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
String[] lines = codeStr.split(ICodeWriter.NL);
|
||||||
for (int i = 0; i < lines.length; i++) {
|
for (int i = 0; i < lines.length; i++) {
|
||||||
String line = lines[i];
|
String line = lines[i];
|
||||||
@@ -316,18 +316,18 @@ public abstract class IntegrationTest extends TestUtils {
|
|||||||
|
|
||||||
private void printCodeWithOffsets(ICodeInfo code) {
|
private void printCodeWithOffsets(ICodeInfo code) {
|
||||||
String codeStr = code.getCodeStr();
|
String codeStr = code.getCodeStr();
|
||||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
ICodeMetadata metadata = code.getCodeMetadata();
|
||||||
String[] lines = codeStr.split(ICodeWriter.NL);
|
int lineStartPos = 0;
|
||||||
for (int i = 0; i < lines.length; i++) {
|
int newLineLen = ICodeWriter.NL.length();
|
||||||
String line = lines[i];
|
for (String line : codeStr.split(ICodeWriter.NL)) {
|
||||||
int curLine = i + 1;
|
Object ann = metadata.getAt(lineStartPos);
|
||||||
Object ann = annotations.get(new CodePosition(curLine, 0));
|
|
||||||
String offsetStr = "";
|
String offsetStr = "";
|
||||||
if (ann instanceof InsnCodeOffset) {
|
if (ann instanceof InsnCodeOffset) {
|
||||||
int offset = ((InsnCodeOffset) ann).getOffset();
|
int offset = ((InsnCodeOffset) ann).getOffset();
|
||||||
offsetStr = "/* " + leftPad(String.valueOf(offset), 5) + " */";
|
offsetStr = "/* " + leftPad(String.valueOf(offset), 5) + " */";
|
||||||
}
|
}
|
||||||
System.out.println(rightPad(offsetStr, 12) + line);
|
System.out.println(rightPad(offsetStr, 12) + line);
|
||||||
|
lineStartPos += line.length() + newLineLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import java.util.Map;
|
|||||||
import org.assertj.core.api.AbstractObjectAssert;
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.ICodeNode;
|
import jadx.core.dex.nodes.ICodeNode;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
@@ -67,8 +67,8 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
|||||||
int codePos = code.getCodeStr().indexOf(refStr);
|
int codePos = code.getCodeStr().indexOf(refStr);
|
||||||
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
assertThat(codePos).describedAs("String '%s' not found", refStr).isNotEqualTo(-1);
|
||||||
int refPos = codePos + refOffset;
|
int refPos = codePos + refOffset;
|
||||||
for (Map.Entry<CodePosition, Object> entry : code.getAnnotations().entrySet()) {
|
for (Map.Entry<Integer, ICodeAnnotation> entry : code.getCodeMetadata().getAsMap().entrySet()) {
|
||||||
if (entry.getKey().getPos() == refPos) {
|
if (entry.getKey() == refPos) {
|
||||||
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
Assertions.assertThat(entry.getValue()).isEqualTo(node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.util.stream.Collectors;
|
|||||||
import org.assertj.core.api.AbstractObjectAssert;
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.data.annotations.ICodeRawOffset;
|
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ public class JadxCodeInfoAssertions extends AbstractObjectAssert<JadxCodeInfoAss
|
|||||||
}
|
}
|
||||||
|
|
||||||
public JadxCodeInfoAssertions checkCodeOffsets() {
|
public JadxCodeInfoAssertions checkCodeOffsets() {
|
||||||
long dupOffsetCount = actual.getAnnotations().values().stream()
|
long dupOffsetCount = actual.getCodeMetadata().getAsMap().values().stream()
|
||||||
.filter(ICodeRawOffset.class::isInstance)
|
.filter(InsnCodeOffset.class::isInstance)
|
||||||
.collect(Collectors.groupingBy(o -> ((ICodeRawOffset) o).getOffset(), Collectors.toList()))
|
.collect(Collectors.groupingBy(o -> ((InsnCodeOffset) o).getOffset(), Collectors.toList()))
|
||||||
.values().stream()
|
.values().stream()
|
||||||
.filter(list -> list.size() > 1)
|
.filter(list -> list.size() > 1)
|
||||||
.count();
|
.count();
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package jadx.tests.external;
|
package jadx.tests.external;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jadx.api.CommentsLevel;
|
import jadx.api.CommentsLevel;
|
||||||
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JadxInternalAccess;
|
import jadx.api.JadxInternalAccess;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
@@ -127,37 +126,15 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void printMethods(ClassNode classNode, @NotNull String mthPattern) {
|
private void printMethods(ClassNode classNode, @NotNull String mthPattern) {
|
||||||
String code = classNode.getCode().getCodeStr();
|
ICodeInfo codeInfo = classNode.getCode();
|
||||||
|
String code = codeInfo.getCodeStr();
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String dashLine = "======================================================================================";
|
String dashLine = "======================================================================================";
|
||||||
Map<Integer, MethodNode> methodsMap = getMethodsMap(classNode);
|
|
||||||
String[] lines = code.split(ICodeWriter.NL);
|
|
||||||
for (MethodNode mth : classNode.getMethods()) {
|
for (MethodNode mth : classNode.getMethods()) {
|
||||||
if (isMthMatch(mth, mthPattern)) {
|
if (isMthMatch(mth, mthPattern)) {
|
||||||
int decompiledLine = mth.getDecompiledLine() - 1;
|
String mthCode = cutMethodCode(codeInfo, code, mth);
|
||||||
StringBuilder mthCode = new StringBuilder();
|
|
||||||
int startLine = getCommentLinesCount(lines, decompiledLine);
|
|
||||||
int brackets = 0;
|
|
||||||
for (int i = startLine; i > 0 && i < lines.length; i++) {
|
|
||||||
// stop if next method started
|
|
||||||
MethodNode mthAtLine = methodsMap.get(i);
|
|
||||||
if (mthAtLine != null && !mthAtLine.equals(mth)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String line = lines[i];
|
|
||||||
mthCode.append(line).append(ICodeWriter.NL);
|
|
||||||
// also count brackets for detect method end
|
|
||||||
if (i >= decompiledLine) {
|
|
||||||
brackets += StringUtils.countMatches(line, '{');
|
|
||||||
brackets -= StringUtils.countMatches(line, '}');
|
|
||||||
if (brackets <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.info("Print method: {}\n{}\n{}\n{}", mth.getMethodInfo().getShortId(),
|
LOG.info("Print method: {}\n{}\n{}\n{}", mth.getMethodInfo().getShortId(),
|
||||||
dashLine,
|
dashLine,
|
||||||
mthCode,
|
mthCode,
|
||||||
@@ -166,22 +143,37 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, MethodNode> getMethodsMap(ClassNode classNode) {
|
@NotNull
|
||||||
Map<Integer, MethodNode> linesMap = new HashMap<>();
|
private String cutMethodCode(ICodeInfo codeInfo, String code, MethodNode mth) {
|
||||||
for (MethodNode method : classNode.getMethods()) {
|
int defPos = mth.getDefPosition();
|
||||||
linesMap.put(method.getDecompiledLine() - 1, method);
|
int startPos = getCommentStartPos(code, defPos);
|
||||||
|
ICodeNodeRef nodeBelow = codeInfo.getCodeMetadata().getNodeBelow(defPos);
|
||||||
|
int stopPos = nodeBelow == null ? code.length() : nodeBelow.getDefPosition();
|
||||||
|
int brackets = 0;
|
||||||
|
StringBuilder mthCode = new StringBuilder();
|
||||||
|
for (int i = startPos; i > 0 && i < stopPos;) {
|
||||||
|
int codePoint = code.codePointAt(i);
|
||||||
|
mthCode.appendCodePoint(codePoint);
|
||||||
|
if (i >= defPos) {
|
||||||
|
// also count brackets for detect method end
|
||||||
|
if (codePoint == '{') {
|
||||||
|
brackets++;
|
||||||
|
} else if (codePoint == '}') {
|
||||||
|
brackets--;
|
||||||
|
if (brackets <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += Character.charCount(codePoint);
|
||||||
}
|
}
|
||||||
return linesMap;
|
return mthCode.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getCommentLinesCount(String[] lines, int line) {
|
protected int getCommentStartPos(String code, int pos) {
|
||||||
for (int i = line - 1; i > 0 && i < lines.length; i--) {
|
String emptyLine = ICodeWriter.NL + ICodeWriter.NL;
|
||||||
String str = lines[i];
|
int emptyLinePos = code.lastIndexOf(emptyLine, pos);
|
||||||
if (str.isEmpty() || str.equals(ICodeWriter.NL)) {
|
return emptyLinePos == -1 ? pos : emptyLinePos + emptyLine.length();
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printErrorReport(JadxDecompiler jadx) {
|
private void printErrorReport(JadxDecompiler jadx) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import jadx.tests.api.IntegrationTest;
|
|||||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
@SuppressWarnings({ "PointlessBooleanExpression", "unused" })
|
||||||
public class TestBitwiseAnd extends IntegrationTest {
|
public class TestBitwiseAnd extends IntegrationTest {
|
||||||
|
|
||||||
public static class TestCls {
|
public static class TestCls {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||||||
|
|
||||||
public class TestConditions14 extends IntegrationTest {
|
public class TestConditions14 extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "EqualsReplaceableByObjectsCall", "ConstantConditions" })
|
||||||
public static class TestCls {
|
public static class TestCls {
|
||||||
|
|
||||||
public static boolean test(Object a, Object b) {
|
public static boolean test(Object a, Object b) {
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue #1455
|
||||||
|
*/
|
||||||
|
public class TestIfCodeStyle extends SmaliTest {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "ConstantConditions", "FieldCanBeLocal", "unused" })
|
||||||
|
public static class TestCls {
|
||||||
|
|
||||||
|
private String moduleName;
|
||||||
|
private String modulePath;
|
||||||
|
private String preinstalledModulePath;
|
||||||
|
private long versionCode;
|
||||||
|
private String versionName;
|
||||||
|
private boolean isFactory;
|
||||||
|
private boolean isActive;
|
||||||
|
|
||||||
|
public void test(Parcel parcel) {
|
||||||
|
int startPos = parcel.dataPosition();
|
||||||
|
int size = parcel.readInt();
|
||||||
|
if (size < 0) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.moduleName = parcel.readString();
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.modulePath = parcel.readString();
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.preinstalledModulePath = parcel.readString();
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.versionCode = parcel.readLong();
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.versionName = parcel.readString();
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isFactory = parcel.readInt() != 0;
|
||||||
|
if (parcel.dataPosition() - startPos >= size) {
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isActive = parcel.readInt() != 0;
|
||||||
|
if (startPos > Integer.MAX_VALUE - size) {
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (startPos <= Integer.MAX_VALUE - size) {
|
||||||
|
parcel.setDataPosition(startPos + size);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Overflow in the size of parcelable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Parcel {
|
||||||
|
public void setDataPosition(int i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public int dataPosition() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readInt() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readString() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long readLong() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
noDebugInfo();
|
||||||
|
assertThat(getClassNode(TestCls.class))
|
||||||
|
.code()
|
||||||
|
.doesNotContain("else")
|
||||||
|
.countString(8, "return;")
|
||||||
|
.containsLines(2,
|
||||||
|
"if (readInt < 0) {",
|
||||||
|
indent() + "if (dataPosition > Integer.MAX_VALUE - readInt) {",
|
||||||
|
indent(2) + "throw new RuntimeException(\"Overflow in the size of parcelable\");",
|
||||||
|
indent() + "}",
|
||||||
|
indent() + "parcel.setDataPosition(dataPosition + readInt);",
|
||||||
|
indent() + "return;",
|
||||||
|
"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmali() {
|
||||||
|
disableCompilation();
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
.doesNotContain("else")
|
||||||
|
.countString(8, "return;")
|
||||||
|
.containsLines(2,
|
||||||
|
"if (_aidl_parcelable_size < 0) {",
|
||||||
|
indent() + "if (_aidl_start_pos > Integer.MAX_VALUE - _aidl_parcelable_size) {",
|
||||||
|
indent(2) + "throw new RuntimeException(\"Overflow in the size of parcelable\");",
|
||||||
|
indent() + "}",
|
||||||
|
indent() + "_aidl_parcel.setDataPosition(_aidl_start_pos + _aidl_parcelable_size);",
|
||||||
|
indent() + "return;",
|
||||||
|
"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package jadx.tests.integration.debuginfo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.utils.CodeUtils;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -42,6 +42,7 @@ public class TestLineNumbers extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
|
printLineNumbers();
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String code = cls.getCode().toString();
|
String code = cls.getCode().toString();
|
||||||
|
|
||||||
@@ -61,19 +62,17 @@ public class TestLineNumbers extends IntegrationTest {
|
|||||||
assertEquals(testClassLine + 20, innerFunc3.getSourceLine());
|
assertEquals(testClassLine + 20, innerFunc3.getSourceLine());
|
||||||
|
|
||||||
// check decompiled lines
|
// check decompiled lines
|
||||||
String[] lines = code.split(ICodeWriter.NL);
|
checkLine(code, field, "int field;");
|
||||||
checkLine(lines, field, "int field;");
|
checkLine(code, func, "public void func() {");
|
||||||
checkLine(lines, func, "public void func() {");
|
checkLine(code, inner, "public static class Inner {");
|
||||||
checkLine(lines, inner, "public static class Inner {");
|
checkLine(code, innerField, "int innerField;");
|
||||||
checkLine(lines, innerField, "int innerField;");
|
checkLine(code, innerFunc, "public void innerFunc() {");
|
||||||
checkLine(lines, innerFunc, "public void innerFunc() {");
|
checkLine(code, innerFunc2, "public void innerFunc2() {");
|
||||||
checkLine(lines, innerFunc2, "public void innerFunc2() {");
|
checkLine(code, innerFunc3, "public void innerFunc3() {");
|
||||||
checkLine(lines, innerFunc3, "public void innerFunc3() {");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkLine(String[] lines, LineAttrNode node, String str) {
|
private static void checkLine(String code, LineAttrNode node, String str) {
|
||||||
int lineNumber = node.getDecompiledLine();
|
String line = CodeUtils.getLineForPos(code, node.getDefPosition());
|
||||||
String line = lines[lineNumber - 1];
|
|
||||||
assertThat(line, containsString(str));
|
assertThat(line, containsString(str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package jadx.tests.integration.debuginfo;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||||
|
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@@ -32,17 +32,16 @@ public class TestLineNumbers2 extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
||||||
public void test() {
|
public void test() {
|
||||||
printLineNumbers();
|
printLineNumbers();
|
||||||
|
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
String linesMapStr = cls.getCode().getLineMapping().toString();
|
String linesMapStr = cls.getCode().getCodeMetadata().getLineMapping().toString();
|
||||||
if (isJavaInput()) {
|
if (isJavaInput()) {
|
||||||
assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 16=25, 18=27, 21=30}", linesMapStr);
|
assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 15=24, 16=25, 18=27, 21=30, 22=31}", linesMapStr);
|
||||||
} else {
|
} else {
|
||||||
// TODO: invert condition to match source lines
|
assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 15=24, 16=25, 17=27, 19=27, 22=30, 23=31}", linesMapStr);
|
||||||
assertEquals("{6=16, 9=17, 12=21, 13=22, 14=23, 15=27, 17=24, 18=25, 19=27, 22=30, 23=31}", linesMapStr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class TestLineNumbers3 extends IntegrationTest {
|
|||||||
public void test() {
|
public void test() {
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
assertThat(cls).code().containsOne("super(message == null ? \"\" : message.toString());");
|
assertThat(cls).code().containsOne("super(message == null ? \"\" : message.toString());");
|
||||||
String linesMapStr = cls.getCode().getLineMapping().toString();
|
String linesMapStr = cls.getCode().getCodeMetadata().getLineMapping().toString();
|
||||||
assertThat(linesMapStr).isEqualTo("{4=13, 5=14, 6=15}");
|
assertThat(linesMapStr).isEqualTo("{4=13, 5=14, 6=15}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
import jadx.api.ICodeWriter;
|
import jadx.api.ICodeWriter;
|
||||||
|
import jadx.api.utils.CodeUtils;
|
||||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
@@ -71,9 +72,10 @@ public class TestReturnSourceLine extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void checkLine(String[] lines, ICodeInfo cw, LineAttrNode node, int offset, String str) {
|
private static void checkLine(String[] lines, ICodeInfo cw, LineAttrNode node, int offset, String str) {
|
||||||
int decompiledLine = node.getDecompiledLine() + offset;
|
int nodeDefLine = CodeUtils.getLineNumForPos(cw.getCodeStr(), node.getDefPosition());
|
||||||
|
int decompiledLine = nodeDefLine + offset;
|
||||||
assertThat(lines[decompiledLine - 1], containsOne(str));
|
assertThat(lines[decompiledLine - 1], containsOne(str));
|
||||||
Integer sourceLine = cw.getLineMapping().get(decompiledLine);
|
Integer sourceLine = cw.getCodeMetadata().getLineMapping().get(decompiledLine);
|
||||||
assertNotNull(sourceLine);
|
assertNotNull(sourceLine);
|
||||||
assertEquals(node.getSourceLine() + offset, (int) sourceLine);
|
assertEquals(node.getSourceLine() + offset, (int) sourceLine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package jadx.tests.integration.java8;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 TestLambdaExtVar extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
|
||||||
|
public void test(List<String> list, String str) {
|
||||||
|
list.removeIf(s -> s.equals(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
List<String> list = new ArrayList<>(Arrays.asList("a", "str", "b"));
|
||||||
|
test(list, "str");
|
||||||
|
assertThat(list).isEqualTo(Arrays.asList("a", "b"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestWithProfiles(TestProfile.DX_J8)
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
assertThat(cls)
|
||||||
|
.code()
|
||||||
|
.doesNotContain("lambda$")
|
||||||
|
.containsOne("return s.equals(str);"); // TODO: simplify to expression
|
||||||
|
|
||||||
|
System.out.println(cls.getCode().getCodeMetadata());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,9 @@ package jadx.tests.integration.loops;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
import static jadx.tests.api.utils.JadxMatchers.countString;
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
|
||||||
import static org.hamcrest.CoreMatchers.not;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
|
|
||||||
public class TestSequentialLoops extends IntegrationTest {
|
public class TestSequentialLoops extends IntegrationTest {
|
||||||
|
|
||||||
@@ -35,13 +30,10 @@ public class TestSequentialLoops extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
disableCompilation();
|
assertThat(getClassNode(TestCls.class))
|
||||||
ClassNode cls = getClassNode(TestCls.class);
|
.code()
|
||||||
String code = cls.getCode().toString();
|
.countString(2, "while (")
|
||||||
|
.containsOne("break;")
|
||||||
assertThat(code, countString(2, "while ("));
|
.containsOne("return c;");
|
||||||
assertThat(code, containsOne("break;"));
|
|
||||||
assertThat(code, containsOne("return c;"));
|
|
||||||
assertThat(code, not(containsString("else")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.api.JavaClass;
|
||||||
|
import jadx.api.JavaMethod;
|
||||||
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
|
import jadx.api.metadata.ICodeMetadata;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.tests.api.IntegrationTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
public class TestCodeMetadata extends IntegrationTest {
|
||||||
|
|
||||||
|
public static class TestCls {
|
||||||
|
public static class A {
|
||||||
|
public String str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String test() {
|
||||||
|
A a = new A();
|
||||||
|
a.str = call();
|
||||||
|
return a.str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String call() {
|
||||||
|
return "str";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ClassNode cls = getClassNode(TestCls.class);
|
||||||
|
assertThat(cls).code().containsOne("return a.str;");
|
||||||
|
|
||||||
|
MethodNode testMth = getMethod(cls, "test");
|
||||||
|
MethodNode callMth = getMethod(cls, "call");
|
||||||
|
|
||||||
|
int callDefPos = callMth.getDefPosition();
|
||||||
|
assertThat(callDefPos).isNotZero();
|
||||||
|
|
||||||
|
JavaClass javaClass = Objects.requireNonNull(jadxDecompiler.getJavaClassByNode(cls));
|
||||||
|
JavaMethod callJavaMethod = Objects.requireNonNull(jadxDecompiler.getJavaMethodByNode(callMth));
|
||||||
|
List<Integer> callUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), callJavaMethod);
|
||||||
|
assertThat(callUsePlaces).hasSize(1);
|
||||||
|
int callUse = callUsePlaces.get(0);
|
||||||
|
|
||||||
|
ICodeMetadata metadata = cls.getCode().getCodeMetadata();
|
||||||
|
ICodeNodeRef callDef = metadata.getNodeAt(callUse);
|
||||||
|
assertThat(callDef).isSameAs(testMth);
|
||||||
|
|
||||||
|
int beforeCallDef = callDefPos - 10;
|
||||||
|
ICodeAnnotation closest = metadata.getClosestUp(beforeCallDef);
|
||||||
|
assertThat(closest).isInstanceOf(FieldNode.class); // field reference from 'return a.str;'
|
||||||
|
|
||||||
|
ICodeNodeRef nodeBelow = metadata.getNodeBelow(beforeCallDef);
|
||||||
|
assertThat(nodeBelow).isSameAs(callMth);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package jadx.tests.integration.others;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jadx.tests.api.SmaliTest;
|
||||||
|
|
||||||
|
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||||
|
|
||||||
|
@SuppressWarnings("CommentedOutCode")
|
||||||
|
public class TestMoveInline extends SmaliTest {
|
||||||
|
// @formatter:off
|
||||||
|
/*
|
||||||
|
public final void Y(int i) throws k {
|
||||||
|
int i2 = 0;
|
||||||
|
while ((i & (-128)) != 0) {
|
||||||
|
this.h[i2] = (byte) ((i & 127) | 128);
|
||||||
|
i >>>= 7;
|
||||||
|
i2++;
|
||||||
|
}
|
||||||
|
byte[] bArr = this.h;
|
||||||
|
bArr[i2] = (byte) i;
|
||||||
|
this.a.k(bArr, 0, i2 + 1);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
getArgs().setRawCFGOutput(true);
|
||||||
|
assertThat(getClassNodeFromSmali())
|
||||||
|
.code()
|
||||||
|
// check operations order
|
||||||
|
.containsLines(3,
|
||||||
|
"i >>>= 7;",
|
||||||
|
"i2++;");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,8 @@ public class TestLoopInTryCatch extends SmaliTest {
|
|||||||
" break;",
|
" break;",
|
||||||
" }",
|
" }",
|
||||||
"}",
|
"}",
|
||||||
"if (i == 1) {",
|
"if (i != 1) {",
|
||||||
|
" getI();",
|
||||||
"}"),
|
"}"),
|
||||||
c -> c.containsLines(2,
|
c -> c.containsLines(2,
|
||||||
"int i;",
|
"int i;",
|
||||||
@@ -37,7 +38,8 @@ public class TestLoopInTryCatch extends SmaliTest {
|
|||||||
" return;",
|
" return;",
|
||||||
" }",
|
" }",
|
||||||
"}",
|
"}",
|
||||||
"if (i == 1) {",
|
"if (i != 1) {",
|
||||||
|
" getI();",
|
||||||
"}"));
|
"}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-11
@@ -1,12 +1,15 @@
|
|||||||
package jadx.tests.integration.variables;
|
package jadx.tests.integration.variables;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.api.data.annotations.VarDeclareRef;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
|
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||||
|
import jadx.api.metadata.annotations.VarNode;
|
||||||
|
import jadx.api.utils.CodeUtils;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.tests.api.IntegrationTest;
|
import jadx.tests.api.IntegrationTest;
|
||||||
@@ -39,14 +42,27 @@ public class TestVariablesDeclAnnotation extends IntegrationTest {
|
|||||||
MethodNode testMth = cls.searchMethodByShortName(mthName);
|
MethodNode testMth = cls.searchMethodByShortName(mthName);
|
||||||
assertThat(testMth).isNotNull();
|
assertThat(testMth).isNotNull();
|
||||||
|
|
||||||
int mthLine = testMth.getDecompiledLine();
|
ICodeInfo codeInfo = cls.getCode();
|
||||||
List<String> argNames = cls.getCode().getAnnotations().entrySet().stream()
|
int mthDefPos = testMth.getDefPosition();
|
||||||
.filter(e -> e.getKey().getLine() == mthLine && e.getValue() instanceof VarDeclareRef)
|
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||||
.sorted(Comparator.comparingInt(e -> e.getKey().getPos()))
|
List<String> argNames2 = new ArrayList<>();
|
||||||
.map(e -> ((VarDeclareRef) e.getValue()).getName())
|
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
|
||||||
.collect(Collectors.toList());
|
if (pos > lineEndPos) {
|
||||||
|
return Boolean.TRUE; // stop at line end
|
||||||
|
}
|
||||||
|
if (ann instanceof NodeDeclareRef) {
|
||||||
|
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
|
||||||
|
if (declRef instanceof VarNode) {
|
||||||
|
VarNode varNode = (VarNode) declRef;
|
||||||
|
if (varNode.getMth().equals(testMth)) {
|
||||||
|
argNames2.add(varNode.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
assertThat(argNames).doesNotContainNull();
|
assertThat(argNames2).doesNotContainNull();
|
||||||
assertThat(argNames.toString()).isEqualTo(expectedVars);
|
assertThat(argNames2.toString()).isEqualTo(expectedVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,354 @@
|
|||||||
|
.class public Lconditions/TestIfCodeStyle;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
.implements Landroid/os/Parcelable;
|
||||||
|
|
||||||
|
|
||||||
|
.field public isActive:Z
|
||||||
|
.field public isFactory:Z
|
||||||
|
.field public moduleName:Ljava/lang/String;
|
||||||
|
.field public modulePath:Ljava/lang/String;
|
||||||
|
.field public preinstalledModulePath:Ljava/lang/String;
|
||||||
|
.field public versionCode:J
|
||||||
|
.field public versionName:Ljava/lang/String;
|
||||||
|
|
||||||
|
|
||||||
|
.method public final readFromParcel(Landroid/os/Parcel;)V
|
||||||
|
.registers 9
|
||||||
|
.param p1, "_aidl_parcel" # Landroid/os/Parcel;
|
||||||
|
|
||||||
|
.line 44
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
.line 45
|
||||||
|
.local v0, "_aidl_start_pos":I
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readInt()I
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
.line 47
|
||||||
|
.local v1, "_aidl_parcelable_size":I
|
||||||
|
const-string v2, "Overflow in the size of parcelable"
|
||||||
|
const v3, 0x7fffffff
|
||||||
|
if-gez v1, :cond_1e
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
|
||||||
|
if-gt v0, v3, :cond_18
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 47
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_18
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 48
|
||||||
|
:cond_1e
|
||||||
|
:try_start_1e
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
:try_end_22
|
||||||
|
.catchall {:try_start_1e .. :try_end_22} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
|
||||||
|
if-lt v4, v1, :cond_34
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-gt v0, v3, :cond_2e
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 48
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_2e
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 49
|
||||||
|
:cond_34
|
||||||
|
:try_start_34
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readString()Ljava/lang/String;
|
||||||
|
move-result-object v4
|
||||||
|
|
||||||
|
iput-object v4, p0, Lconditions/TestIfCodeStyle;->moduleName:Ljava/lang/String;
|
||||||
|
|
||||||
|
.line 50
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v4
|
||||||
|
:try_end_3e
|
||||||
|
.catchall {:try_start_34 .. :try_end_3e} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
|
||||||
|
if-lt v4, v1, :cond_50
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
|
||||||
|
if-gt v0, v3, :cond_4a
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 50
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_4a
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 51
|
||||||
|
:cond_50
|
||||||
|
:try_start_50
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readString()Ljava/lang/String;
|
||||||
|
move-result-object v4
|
||||||
|
iput-object v4, p0, Lconditions/TestIfCodeStyle;->modulePath:Ljava/lang/String;
|
||||||
|
|
||||||
|
.line 52
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
:try_end_5a
|
||||||
|
.catchall {:try_start_50 .. :try_end_5a} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
if-lt v4, v1, :cond_6c
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-gt v0, v3, :cond_66
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 52
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_66
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 53
|
||||||
|
:cond_6c
|
||||||
|
:try_start_6c
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readString()Ljava/lang/String;
|
||||||
|
move-result-object v4
|
||||||
|
iput-object v4, p0, Lconditions/TestIfCodeStyle;->preinstalledModulePath:Ljava/lang/String;
|
||||||
|
|
||||||
|
.line 54
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
:try_end_76
|
||||||
|
.catchall {:try_start_6c .. :try_end_76} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
if-lt v4, v1, :cond_88
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-gt v0, v3, :cond_82
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 54
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_82
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 55
|
||||||
|
:cond_88
|
||||||
|
:try_start_88
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readLong()J
|
||||||
|
move-result-wide v4
|
||||||
|
iput-wide v4, p0, Lconditions/TestIfCodeStyle;->versionCode:J
|
||||||
|
|
||||||
|
.line 56
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
:try_end_92
|
||||||
|
.catchall {:try_start_88 .. :try_end_92} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
if-lt v4, v1, :cond_a4
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-gt v0, v3, :cond_9e
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 56
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_9e
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 57
|
||||||
|
:cond_a4
|
||||||
|
:try_start_a4
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readString()Ljava/lang/String;
|
||||||
|
move-result-object v4
|
||||||
|
iput-object v4, p0, Lconditions/TestIfCodeStyle;->versionName:Ljava/lang/String;
|
||||||
|
|
||||||
|
.line 58
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
move-result v4
|
||||||
|
:try_end_ae
|
||||||
|
.catchall {:try_start_a4 .. :try_end_ae} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
if-lt v4, v1, :cond_c0
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-gt v0, v3, :cond_ba
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 58
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_ba
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 59
|
||||||
|
:cond_c0
|
||||||
|
:try_start_c0
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readInt()I
|
||||||
|
move-result v4
|
||||||
|
const/4 v5, 0x1
|
||||||
|
const/4 v6, 0x0
|
||||||
|
if-eqz v4, :cond_ca
|
||||||
|
move v4, v5
|
||||||
|
goto :goto_cb
|
||||||
|
|
||||||
|
:cond_ca
|
||||||
|
move v4, v6
|
||||||
|
|
||||||
|
:goto_cb
|
||||||
|
iput-boolean v4, p0, Lconditions/TestIfCodeStyle;->isFactory:Z
|
||||||
|
|
||||||
|
.line 60
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->dataPosition()I
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
:try_end_d1
|
||||||
|
.catchall {:try_start_c0 .. :try_end_d1} :catchall_fd
|
||||||
|
|
||||||
|
sub-int/2addr v4, v0
|
||||||
|
if-lt v4, v1, :cond_e3
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
|
||||||
|
if-gt v0, v3, :cond_dd
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 60
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_dd
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 61
|
||||||
|
:cond_e3
|
||||||
|
:try_start_e3
|
||||||
|
invoke-virtual {p1}, Landroid/os/Parcel;->readInt()I
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
if-eqz v4, :cond_ea
|
||||||
|
goto :goto_eb
|
||||||
|
|
||||||
|
:cond_ea
|
||||||
|
move v5, v6
|
||||||
|
|
||||||
|
:goto_eb
|
||||||
|
iput-boolean v5, p0, Lconditions/TestIfCodeStyle;->isActive:Z
|
||||||
|
:try_end_ed
|
||||||
|
.catchall {:try_start_e3 .. :try_end_ed} :catchall_fd
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
|
||||||
|
if-gt v0, v3, :cond_f7
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 67
|
||||||
|
nop
|
||||||
|
|
||||||
|
.line 68
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
:cond_f7
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 63
|
||||||
|
:catchall_fd
|
||||||
|
move-exception v4
|
||||||
|
sub-int/2addr v3, v1
|
||||||
|
if-le v0, v3, :cond_107
|
||||||
|
|
||||||
|
.line 64
|
||||||
|
new-instance v3, Ljava/lang/RuntimeException;
|
||||||
|
invoke-direct {v3, v2}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
|
||||||
|
throw v3
|
||||||
|
|
||||||
|
.line 66
|
||||||
|
:cond_107
|
||||||
|
add-int v2, v0, v1
|
||||||
|
invoke-virtual {p1, v2}, Landroid/os/Parcel;->setDataPosition(I)V
|
||||||
|
|
||||||
|
.line 67
|
||||||
|
throw v4
|
||||||
|
.end method
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
.class public Lothers/TestMoveInline;
|
||||||
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
|
.field private h:[B
|
||||||
|
.field private a:Lothers/TestMoveInline;
|
||||||
|
|
||||||
|
.method public k([BII)V
|
||||||
|
.registers 5
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public test(I)V
|
||||||
|
.registers 7
|
||||||
|
|
||||||
|
const/4 v0, 0x0
|
||||||
|
move v1, v0
|
||||||
|
|
||||||
|
:goto_2
|
||||||
|
and-int/lit8 v2, p1, -0x80
|
||||||
|
if-nez v2, :cond_13
|
||||||
|
|
||||||
|
.line 1
|
||||||
|
iget-object v2, p0, Lothers/TestMoveInline;->h:[B
|
||||||
|
add-int/lit8 v3, v1, 0x1
|
||||||
|
int-to-byte p1, p1
|
||||||
|
aput-byte p1, v2, v1
|
||||||
|
|
||||||
|
.line 2
|
||||||
|
iget-object p1, p0, Lothers/TestMoveInline;->a:Lothers/TestMoveInline;
|
||||||
|
invoke-virtual {p1, v2, v0, v3}, Lothers/TestMoveInline;->k([BII)V
|
||||||
|
return-void
|
||||||
|
|
||||||
|
.line 3
|
||||||
|
:cond_13
|
||||||
|
iget-object v2, p0, Lothers/TestMoveInline;->h:[B
|
||||||
|
add-int/lit8 v3, v1, 0x1
|
||||||
|
and-int/lit8 v4, p1, 0x7f
|
||||||
|
or-int/lit16 v4, v4, 0x80
|
||||||
|
int-to-byte v4, v4
|
||||||
|
aput-byte v4, v2, v1
|
||||||
|
ushr-int/lit8 p1, p1, 0x7
|
||||||
|
move v1, v3
|
||||||
|
goto :goto_2
|
||||||
|
.end method
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
:cond
|
:cond
|
||||||
if-eq v1, v2, :end
|
if-eq v1, v2, :end
|
||||||
|
invoke-static {}, Ltrycatch/TestLoopInTryCatch;->getI()I
|
||||||
return-void
|
return-void
|
||||||
|
|
||||||
:try_end
|
:try_end
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ dependencies {
|
|||||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||||
|
|
||||||
implementation 'com.formdev:flatlaf:2.1'
|
implementation 'com.formdev:flatlaf:2.2'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:2.1'
|
implementation 'com.formdev:flatlaf-intellij-themes:2.2'
|
||||||
implementation 'com.formdev:flatlaf-extras:2.1'
|
implementation 'com.formdev:flatlaf-extras:2.2'
|
||||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
@@ -29,6 +29,8 @@ dependencies {
|
|||||||
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
||||||
implementation 'com.android.tools.build:apksig:4.2.1'
|
implementation 'com.android.tools.build:apksig:4.2.1'
|
||||||
implementation 'io.github.hqktech:jdwp:1.0'
|
implementation 'io.github.hqktech:jdwp:1.0'
|
||||||
|
|
||||||
|
testImplementation project(":jadx-core").sourceSets.test.output
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@@ -98,7 +100,7 @@ runtime {
|
|||||||
addModules(
|
addModules(
|
||||||
'java.desktop',
|
'java.desktop',
|
||||||
'java.naming',
|
'java.naming',
|
||||||
'java.sql', // TODO: GSON register adapter for java.sql.Time
|
//'java.sql', // TODO: GSON register adapter for java.sql.Time
|
||||||
'java.xml',
|
'java.xml',
|
||||||
)
|
)
|
||||||
jpackage {
|
jpackage {
|
||||||
|
|||||||
@@ -11,20 +11,26 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.api.JavaPackage;
|
import jadx.api.JavaPackage;
|
||||||
import jadx.api.ResourceFile;
|
import jadx.api.ResourceFile;
|
||||||
|
import jadx.api.impl.InMemoryCodeCache;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.ProcessState;
|
import jadx.core.dex.nodes.ProcessState;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
import jadx.gui.settings.JadxProject;
|
import jadx.gui.settings.JadxProject;
|
||||||
import jadx.gui.settings.JadxSettings;
|
import jadx.gui.settings.JadxSettings;
|
||||||
|
import jadx.gui.utils.codecache.CodeStringCache;
|
||||||
|
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
||||||
|
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||||
|
|
||||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||||
import static jadx.gui.utils.FileUtils.toFiles;
|
|
||||||
|
|
||||||
public class JadxWrapper {
|
public class JadxWrapper {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
|
||||||
@@ -44,12 +50,14 @@ public class JadxWrapper {
|
|||||||
this.openPaths = paths;
|
this.openPaths = paths;
|
||||||
try {
|
try {
|
||||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
JadxArgs jadxArgs = settings.toJadxArgs();
|
||||||
jadxArgs.setInputFiles(toFiles(paths));
|
jadxArgs.setInputFiles(FileUtils.toFiles(paths));
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
jadxArgs.setCodeData(project.getCodeData());
|
jadxArgs.setCodeData(project.getCodeData());
|
||||||
}
|
}
|
||||||
|
closeCodeCache();
|
||||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||||
this.decompiler.load();
|
this.decompiler.load();
|
||||||
|
initCodeCache(jadxArgs);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Jadx init error", e);
|
LOG.error("Jadx init error", e);
|
||||||
close();
|
close();
|
||||||
@@ -68,12 +76,55 @@ public class JadxWrapper {
|
|||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
decompiler.close();
|
decompiler.close();
|
||||||
|
closeCodeCache();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx decompiler close error", e);
|
LOG.error("jadx decompiler close error", e);
|
||||||
}
|
}
|
||||||
this.openPaths = Collections.emptyList();
|
this.openPaths = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initCodeCache(JadxArgs jadxArgs) {
|
||||||
|
switch (settings.getCodeCacheMode()) {
|
||||||
|
case MEMORY:
|
||||||
|
jadxArgs.setCodeCache(new InMemoryCodeCache());
|
||||||
|
break;
|
||||||
|
case DISK_WITH_CACHE:
|
||||||
|
jadxArgs.setCodeCache(new CodeStringCache(buildBufferedDiskCache()));
|
||||||
|
break;
|
||||||
|
case DISK:
|
||||||
|
jadxArgs.setCodeCache(buildBufferedDiskCache());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferCodeCache buildBufferedDiskCache() {
|
||||||
|
DiskCodeCache diskCache = new DiskCodeCache(decompiler.getRoot(), getCacheDir());
|
||||||
|
return new BufferCodeCache(diskCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getCacheDir() {
|
||||||
|
if (project != null && project.getProjectPath() != null) {
|
||||||
|
Path projectPath = project.getProjectPath();
|
||||||
|
return projectPath.resolveSibling(projectPath.getFileName() + ".cache");
|
||||||
|
}
|
||||||
|
if (!openPaths.isEmpty()) {
|
||||||
|
Path path = openPaths.get(0);
|
||||||
|
return path.resolveSibling(path.getFileName() + ".cache");
|
||||||
|
}
|
||||||
|
throw new JadxRuntimeException("Can't get working dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeCodeCache() {
|
||||||
|
ICodeCache codeCache = getArgs().getCodeCache();
|
||||||
|
if (codeCache != null) {
|
||||||
|
try {
|
||||||
|
codeCache.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JadxRuntimeException("Error on cache close", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the complete list of classes
|
* Get the complete list of classes
|
||||||
*/
|
*/
|
||||||
@@ -90,16 +141,34 @@ public class JadxWrapper {
|
|||||||
if (excludedPackages.isEmpty()) {
|
if (excludedPackages.isEmpty()) {
|
||||||
return classList;
|
return classList;
|
||||||
}
|
}
|
||||||
|
return classList.stream()
|
||||||
|
.filter(cls -> isClassIncluded(excludedPackages, cls))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
return classList.stream().filter(cls -> {
|
/**
|
||||||
for (String exclude : excludedPackages) {
|
* Get all classes that are not excluded by the excluded packages settings including inner classes
|
||||||
if (cls.getFullName().equals(exclude)
|
*/
|
||||||
|| cls.getFullName().startsWith(exclude + '.')) {
|
public List<JavaClass> getIncludedClassesWithInners() {
|
||||||
return false;
|
List<JavaClass> classes = decompiler.getClassesWithInners();
|
||||||
}
|
List<String> excludedPackages = getExcludedPackages();
|
||||||
|
if (excludedPackages.isEmpty()) {
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
return classes.stream()
|
||||||
|
.filter(cls -> isClassIncluded(excludedPackages, cls))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isClassIncluded(List<String> excludedPackages, JavaClass cls) {
|
||||||
|
for (String exclude : excludedPackages) {
|
||||||
|
String clsFullName = cls.getFullName();
|
||||||
|
if (clsFullName.equals(exclude)
|
||||||
|
|| clsFullName.startsWith(exclude + '.')) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}).collect(Collectors.toList());
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<List<JavaClass>> buildDecompileBatches(List<JavaClass> classes) {
|
public List<List<JavaClass>> buildDecompileBatches(List<JavaClass> classes) {
|
||||||
@@ -158,7 +227,8 @@ public class JadxWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param fullName Full name of an outer class. Inner classes are not supported.
|
* @param fullName
|
||||||
|
* Full name of an outer class. Inner classes are not supported.
|
||||||
*/
|
*/
|
||||||
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
||||||
return decompiler.getClasses().stream()
|
return decompiler.getClasses().stream()
|
||||||
@@ -172,7 +242,8 @@ public class JadxWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param rawName Full raw name of an outer class. Inner classes are not supported.
|
* @param rawName
|
||||||
|
* Full raw name of an outer class. Inner classes are not supported.
|
||||||
*/
|
*/
|
||||||
public @Nullable JavaClass searchJavaClassByRawName(String rawName) {
|
public @Nullable JavaClass searchJavaClassByRawName(String rawName) {
|
||||||
return decompiler.getClasses().stream()
|
return decompiler.getClasses().stream()
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package jadx.gui.device.debugger.smali;
|
package jadx.gui.device.debugger.smali;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jadx.api.CodePosition;
|
|
||||||
import jadx.api.ICodeInfo;
|
import jadx.api.ICodeInfo;
|
||||||
|
import jadx.api.impl.SimpleCodeInfo;
|
||||||
import jadx.api.impl.SimpleCodeWriter;
|
import jadx.api.impl.SimpleCodeWriter;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
|
||||||
@@ -34,21 +31,6 @@ public class SmaliWriter extends SimpleCodeWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICodeInfo finish() {
|
public ICodeInfo finish() {
|
||||||
return new ICodeInfo() {
|
return new SimpleCodeInfo(buf.toString());
|
||||||
@Override
|
|
||||||
public String getCodeStr() {
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,19 @@ package jadx.gui.jobs;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -21,6 +24,8 @@ import jadx.gui.ui.panel.ProgressPanel;
|
|||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
import jadx.gui.utils.UiUtils;
|
import jadx.gui.utils.UiUtils;
|
||||||
|
|
||||||
|
import static jadx.gui.utils.UiUtils.calcProgress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for run tasks in background with progress bar indication.
|
* Class for run tasks in background with progress bar indication.
|
||||||
* Use instance created in {@link MainWindow}.
|
* Use instance created in {@link MainWindow}.
|
||||||
@@ -32,15 +37,19 @@ public class BackgroundExecutor {
|
|||||||
private final ProgressPanel progressPane;
|
private final ProgressPanel progressPane;
|
||||||
|
|
||||||
private ThreadPoolExecutor taskQueueExecutor;
|
private ThreadPoolExecutor taskQueueExecutor;
|
||||||
|
private final Map<Long, IBackgroundTask> taskRunning = new ConcurrentHashMap<>();
|
||||||
|
private final AtomicLong idSupplier = new AtomicLong(0);
|
||||||
|
|
||||||
public BackgroundExecutor(MainWindow mainWindow) {
|
public BackgroundExecutor(MainWindow mainWindow) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.progressPane = mainWindow.getProgressPane();
|
this.progressPane = mainWindow.getProgressPane();
|
||||||
this.taskQueueExecutor = makeTaskQueueExecutor();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<TaskStatus> execute(IBackgroundTask task) {
|
public synchronized Future<TaskStatus> execute(IBackgroundTask task) {
|
||||||
TaskWorker taskWorker = new TaskWorker(task);
|
long id = idSupplier.incrementAndGet();
|
||||||
|
TaskWorker taskWorker = new TaskWorker(id, task);
|
||||||
|
taskRunning.put(id, task);
|
||||||
taskQueueExecutor.execute(() -> {
|
taskQueueExecutor.execute(() -> {
|
||||||
taskWorker.init();
|
taskWorker.init();
|
||||||
taskWorker.run();
|
taskWorker.run();
|
||||||
@@ -56,15 +65,16 @@ public class BackgroundExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelAll() {
|
public synchronized void cancelAll() {
|
||||||
try {
|
try {
|
||||||
taskQueueExecutor.shutdownNow();
|
taskRunning.values().forEach(Cancelable::cancel);
|
||||||
boolean complete = taskQueueExecutor.awaitTermination(2, TimeUnit.SECONDS);
|
taskQueueExecutor.shutdown();
|
||||||
|
boolean complete = taskQueueExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
||||||
LOG.debug("Background task executor terminated with status: {}", complete ? "complete" : "interrupted");
|
LOG.debug("Background task executor terminated with status: {}", complete ? "complete" : "interrupted");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error terminating task executor", e);
|
LOG.error("Error terminating task executor", e);
|
||||||
} finally {
|
} finally {
|
||||||
taskQueueExecutor = makeTaskQueueExecutor();
|
reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,14 +87,21 @@ public class BackgroundExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Future<TaskStatus> execute(String title, Runnable backgroundRunnable) {
|
public Future<TaskStatus> execute(String title, Runnable backgroundRunnable) {
|
||||||
return execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable), null));
|
return execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThreadPoolExecutor makeTaskQueueExecutor() {
|
private synchronized void reset() {
|
||||||
return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
|
taskQueueExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
|
||||||
|
taskRunning.clear();
|
||||||
|
idSupplier.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void taskComplete(long id) {
|
||||||
|
taskRunning.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class TaskWorker extends SwingWorker<TaskStatus, Void> implements ITaskInfo {
|
private final class TaskWorker extends SwingWorker<TaskStatus, Void> implements ITaskInfo {
|
||||||
|
private final long id;
|
||||||
private final IBackgroundTask task;
|
private final IBackgroundTask task;
|
||||||
private ThreadPoolExecutor executor;
|
private ThreadPoolExecutor executor;
|
||||||
private TaskStatus status = TaskStatus.WAIT;
|
private TaskStatus status = TaskStatus.WAIT;
|
||||||
@@ -92,26 +109,36 @@ public class BackgroundExecutor {
|
|||||||
private long jobsComplete;
|
private long jobsComplete;
|
||||||
private long time;
|
private long time;
|
||||||
|
|
||||||
public TaskWorker(IBackgroundTask task) {
|
public TaskWorker(long id, IBackgroundTask task) {
|
||||||
|
this.id = id;
|
||||||
this.task = task;
|
this.task = task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
addPropertyChangeListener(progressPane);
|
addPropertyChangeListener(progressPane);
|
||||||
progressPane.reset();
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
progressPane.reset();
|
||||||
|
if (task.getTaskProgress() != null) {
|
||||||
|
progressPane.setIndeterminate(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TaskStatus doInBackground() throws Exception {
|
protected TaskStatus doInBackground() throws Exception {
|
||||||
progressPane.changeLabel(this, task.getTitle() + "… ");
|
progressPane.changeLabel(this, task.getTitle() + "… ");
|
||||||
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
|
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
|
||||||
|
try {
|
||||||
runJobs();
|
runJobs();
|
||||||
|
} finally {
|
||||||
|
taskComplete(id);
|
||||||
|
task.onDone(this);
|
||||||
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runJobs() throws InterruptedException {
|
private void runJobs() throws InterruptedException {
|
||||||
List<Runnable> jobs = task.scheduleJobs();
|
List<? extends Runnable> jobs = task.scheduleJobs();
|
||||||
jobsCount = jobs.size();
|
jobsCount = jobs.size();
|
||||||
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
|
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
|
||||||
task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage());
|
task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage());
|
||||||
@@ -129,7 +156,6 @@ public class BackgroundExecutor {
|
|||||||
status = waitTermination(executor, buildCancelCheck(startTime));
|
status = waitTermination(executor, buildCancelCheck(startTime));
|
||||||
time = System.currentTimeMillis() - startTime;
|
time = System.currentTimeMillis() - startTime;
|
||||||
jobsComplete = executor.getCompletedTaskCount();
|
jobsComplete = executor.getCompletedTaskCount();
|
||||||
task.onDone(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("BusyWait")
|
@SuppressWarnings("BusyWait")
|
||||||
@@ -145,9 +171,9 @@ public class BackgroundExecutor {
|
|||||||
performCancel(executor);
|
performCancel(executor);
|
||||||
return cancelStatus;
|
return cancelStatus;
|
||||||
}
|
}
|
||||||
setProgress(calcProgress(executor.getCompletedTaskCount()));
|
updateProgress(executor);
|
||||||
k++;
|
k++;
|
||||||
Thread.sleep(k < 20 ? 100 : 1000); // faster update for short tasks
|
Thread.sleep(k < 10 ? 200 : 1000); // faster update for short tasks
|
||||||
if (jobsCount == 1 && k == 3) {
|
if (jobsCount == 1 && k == 3) {
|
||||||
// small delay before show progress to reduce blinking on short tasks
|
// small delay before show progress to reduce blinking on short tasks
|
||||||
progressPane.changeVisibility(this, true);
|
progressPane.changeVisibility(this, true);
|
||||||
@@ -164,19 +190,39 @@ public class BackgroundExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateProgress(ThreadPoolExecutor executor) {
|
||||||
|
Consumer<ITaskProgress> onProgressListener = task.getOnProgressListener();
|
||||||
|
ITaskProgress taskProgress = task.getTaskProgress();
|
||||||
|
if (taskProgress == null) {
|
||||||
|
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||||
|
if (onProgressListener != null) {
|
||||||
|
onProgressListener.accept(new TaskProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setProgress(calcProgress(taskProgress));
|
||||||
|
if (onProgressListener != null) {
|
||||||
|
onProgressListener.accept(taskProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void performCancel(ThreadPoolExecutor executor) throws InterruptedException {
|
private void performCancel(ThreadPoolExecutor executor) throws InterruptedException {
|
||||||
progressPane.changeLabel(this, task.getTitle() + " (" + NLS.str("progress.canceling") + ")… ");
|
progressPane.changeLabel(this, task.getTitle() + " (" + NLS.str("progress.canceling") + ")… ");
|
||||||
progressPane.changeIndeterminate(this, true);
|
progressPane.changeIndeterminate(this, true);
|
||||||
// force termination
|
// force termination
|
||||||
executor.shutdownNow();
|
task.cancel();
|
||||||
|
executor.shutdown();
|
||||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
LOG.debug("Task cancel complete: {}", complete);
|
LOG.debug("Task cancel complete: {}", complete ? "success" : "aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Supplier<TaskStatus> buildCancelCheck(long startTime) {
|
private Supplier<TaskStatus> buildCancelCheck(long startTime) {
|
||||||
long waitUntilTime = task.timeLimit() == 0 ? 0 : startTime + task.timeLimit();
|
long waitUntilTime = task.timeLimit() == 0 ? 0 : startTime + task.timeLimit();
|
||||||
boolean checkMemoryUsage = task.checkMemoryUsage();
|
boolean checkMemoryUsage = task.checkMemoryUsage();
|
||||||
return () -> {
|
return () -> {
|
||||||
|
if (task.isCanceled()) {
|
||||||
|
return TaskStatus.CANCEL_BY_USER;
|
||||||
|
}
|
||||||
if (waitUntilTime != 0 && waitUntilTime < System.currentTimeMillis()) {
|
if (waitUntilTime != 0 && waitUntilTime < System.currentTimeMillis()) {
|
||||||
LOG.error("Task '{}' execution timeout, force cancel", task.getTitle());
|
LOG.error("Task '{}' execution timeout, force cancel", task.getTitle());
|
||||||
return TaskStatus.CANCEL_BY_TIMEOUT;
|
return TaskStatus.CANCEL_BY_TIMEOUT;
|
||||||
@@ -205,10 +251,6 @@ public class BackgroundExecutor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calcProgress(long done) {
|
|
||||||
return Math.round(done * 100 / (float) jobsCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
progressPane.setVisible(false);
|
progressPane.setVisible(false);
|
||||||
@@ -240,38 +282,4 @@ public class BackgroundExecutor {
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class SimpleTask implements IBackgroundTask {
|
|
||||||
private final String title;
|
|
||||||
private final List<Runnable> jobs;
|
|
||||||
private final Consumer<TaskStatus> onFinish;
|
|
||||||
|
|
||||||
public SimpleTask(String title, List<Runnable> jobs, @Nullable Consumer<TaskStatus> onFinish) {
|
|
||||||
this.title = title;
|
|
||||||
this.jobs = jobs;
|
|
||||||
this.onFinish = onFinish;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Runnable> scheduleJobs() {
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish(ITaskInfo taskInfo) {
|
|
||||||
if (onFinish != null) {
|
|
||||||
onFinish.accept(taskInfo.getStatus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkMemoryUsage() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
|
public interface Cancelable {
|
||||||
|
boolean isCanceled();
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public abstract class CancelableBackgroundTask implements IBackgroundTask {
|
||||||
|
|
||||||
|
private final AtomicBoolean cancel = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCanceled() {
|
||||||
|
return cancel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
cancel.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetCancel() {
|
||||||
|
cancel.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeCanceled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jadx.api.ICodeCache;
|
||||||
import jadx.api.JavaClass;
|
import jadx.api.JavaClass;
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.ui.MainWindow;
|
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
import jadx.gui.utils.UiUtils;
|
import jadx.gui.utils.UiUtils;
|
||||||
|
|
||||||
public class DecompileTask implements IBackgroundTask {
|
public class DecompileTask extends CancelableBackgroundTask {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DecompileTask.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DecompileTask.class);
|
||||||
|
|
||||||
private static final int CLS_LIMIT = Integer.parseInt(UiUtils.getEnvVar("JADX_CLS_PROCESS_LIMIT", "50"));
|
private static final int CLS_LIMIT = Integer.parseInt(UiUtils.getEnvVar("JADX_CLS_PROCESS_LIMIT", "50"));
|
||||||
@@ -23,15 +23,13 @@ public class DecompileTask implements IBackgroundTask {
|
|||||||
return classCount * CLS_LIMIT + 5000;
|
return classCount * CLS_LIMIT + 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
|
||||||
private final JadxWrapper wrapper;
|
private final JadxWrapper wrapper;
|
||||||
private final AtomicInteger complete = new AtomicInteger(0);
|
private final AtomicInteger complete = new AtomicInteger(0);
|
||||||
private int expectedCompleteCount;
|
private int expectedCompleteCount;
|
||||||
|
|
||||||
private ProcessResult result;
|
private ProcessResult result;
|
||||||
|
|
||||||
public DecompileTask(MainWindow mainWindow, JadxWrapper wrapper) {
|
public DecompileTask(JadxWrapper wrapper) {
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +40,8 @@ public class DecompileTask implements IBackgroundTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Runnable> scheduleJobs() {
|
public List<Runnable> scheduleJobs() {
|
||||||
IndexService indexService = mainWindow.getCacheObject().getIndexService();
|
|
||||||
List<JavaClass> classes = wrapper.getIncludedClasses();
|
List<JavaClass> classes = wrapper.getIncludedClasses();
|
||||||
expectedCompleteCount = classes.size();
|
expectedCompleteCount = classes.size();
|
||||||
|
|
||||||
indexService.setComplete(false);
|
|
||||||
complete.set(0);
|
complete.set(0);
|
||||||
|
|
||||||
List<List<JavaClass>> batches;
|
List<List<JavaClass>> batches;
|
||||||
@@ -56,12 +51,18 @@ public class DecompileTask implements IBackgroundTask {
|
|||||||
LOG.error("Decompile batches build error", e);
|
LOG.error("Decompile batches build error", e);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
ICodeCache codeCache = wrapper.getArgs().getCodeCache();
|
||||||
List<Runnable> jobs = new ArrayList<>(batches.size());
|
List<Runnable> jobs = new ArrayList<>(batches.size());
|
||||||
for (List<JavaClass> batch : batches) {
|
for (List<JavaClass> batch : batches) {
|
||||||
jobs.add(() -> {
|
jobs.add(() -> {
|
||||||
for (JavaClass cls : batch) {
|
for (JavaClass cls : batch) {
|
||||||
|
if (isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
cls.decompile();
|
if (!codeCache.contains(cls.getRawName())) {
|
||||||
|
cls.decompile();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOG.error("Failed to decompile class: {}", cls, e);
|
LOG.error("Failed to decompile class: {}", cls, e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -80,7 +81,7 @@ public class DecompileTask implements IBackgroundTask {
|
|||||||
int timeLimit = timeLimit();
|
int timeLimit = timeLimit();
|
||||||
int skippedCls = expectedCompleteCount - complete.get();
|
int skippedCls = expectedCompleteCount - complete.get();
|
||||||
if (LOG.isInfoEnabled()) {
|
if (LOG.isInfoEnabled()) {
|
||||||
LOG.info("Decompile task complete in " + taskTime + " ms (avg " + avgPerCls + " ms per class)"
|
LOG.info("Decompile and index task complete in " + taskTime + " ms (avg " + avgPerCls + " ms per class)"
|
||||||
+ ", classes: " + expectedCompleteCount
|
+ ", classes: " + expectedCompleteCount
|
||||||
+ ", skipped: " + skippedCls
|
+ ", skipped: " + skippedCls
|
||||||
+ ", time limit:{ total: " + timeLimit + "ms, per cls: " + CLS_LIMIT + "ms }"
|
+ ", time limit:{ total: " + timeLimit + "ms, per cls: " + CLS_LIMIT + "ms }"
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import jadx.api.ICodeCache;
|
|||||||
import jadx.api.JadxDecompiler;
|
import jadx.api.JadxDecompiler;
|
||||||
import jadx.gui.JadxWrapper;
|
import jadx.gui.JadxWrapper;
|
||||||
import jadx.gui.ui.MainWindow;
|
import jadx.gui.ui.MainWindow;
|
||||||
import jadx.gui.utils.FixedCodeCache;
|
|
||||||
import jadx.gui.utils.NLS;
|
import jadx.gui.utils.NLS;
|
||||||
|
import jadx.gui.utils.codecache.FixedCodeCache;
|
||||||
|
|
||||||
public class ExportTask implements IBackgroundTask {
|
public class ExportTask extends CancelableBackgroundTask {
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
private final MainWindow mainWindow;
|
||||||
private final JadxWrapper wrapper;
|
private final JadxWrapper wrapper;
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package jadx.gui.jobs;
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface IBackgroundTask {
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface IBackgroundTask extends Cancelable {
|
||||||
|
|
||||||
String getTitle();
|
String getTitle();
|
||||||
|
|
||||||
List<Runnable> scheduleJobs();
|
/**
|
||||||
|
* Jobs to run in parallel
|
||||||
|
*/
|
||||||
|
List<? extends Runnable> scheduleJobs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called on executor thread after the all jobs finished.
|
* Called on executor thread after the all jobs finished.
|
||||||
@@ -37,4 +43,18 @@ public interface IBackgroundTask {
|
|||||||
default boolean checkMemoryUsage() {
|
default boolean checkMemoryUsage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get task progress (Optional)
|
||||||
|
*/
|
||||||
|
default @Nullable ITaskProgress getTaskProgress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return progress notifications listener (use executor tick rate and thread) (Optional)
|
||||||
|
*/
|
||||||
|
default @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
|
public interface ITaskProgress {
|
||||||
|
|
||||||
|
int progress();
|
||||||
|
|
||||||
|
int total();
|
||||||
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package jadx.gui.jobs;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.ICodeInfo;
|
|
||||||
import jadx.api.ICodeWriter;
|
|
||||||
import jadx.api.JavaClass;
|
|
||||||
import jadx.gui.utils.CacheObject;
|
|
||||||
import jadx.gui.utils.CodeLinesInfo;
|
|
||||||
import jadx.gui.utils.search.StringRef;
|
|
||||||
import jadx.gui.utils.search.TextSearchIndex;
|
|
||||||
|
|
||||||
public class IndexService {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(IndexService.class);
|
|
||||||
|
|
||||||
private final CacheObject cache;
|
|
||||||
private boolean indexComplete;
|
|
||||||
private final Set<JavaClass> indexSet = new HashSet<>();
|
|
||||||
|
|
||||||
public IndexService(CacheObject cache) {
|
|
||||||
this.cache = cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning! Not ready for parallel execution. Use only in a single thread.
|
|
||||||
*/
|
|
||||||
public boolean indexCls(JavaClass cls) {
|
|
||||||
try {
|
|
||||||
TextSearchIndex index = cache.getTextIndex();
|
|
||||||
if (index == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// get code from cache to avoid decompilation here
|
|
||||||
String code = getCodeFromCache(cls);
|
|
||||||
if (code == null) {
|
|
||||||
return cls.isNoCode();
|
|
||||||
}
|
|
||||||
List<StringRef> lines = splitLines(code);
|
|
||||||
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
|
|
||||||
index.indexCode(cls, linesInfo, lines);
|
|
||||||
index.indexNames(cls);
|
|
||||||
indexSet.add(cls);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Index error in class: {}", cls.getFullName(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add to API
|
|
||||||
@Nullable
|
|
||||||
private String getCodeFromCache(JavaClass cls) {
|
|
||||||
ICodeInfo codeInfo = cls.getClassNode().getCodeFromCache();
|
|
||||||
return codeInfo != null ? codeInfo.getCodeStr() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void indexResources() {
|
|
||||||
TextSearchIndex index = cache.getTextIndex();
|
|
||||||
index.indexResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void refreshIndex(JavaClass cls) {
|
|
||||||
TextSearchIndex index = cache.getTextIndex();
|
|
||||||
if (index == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
indexSet.remove(cls);
|
|
||||||
index.remove(cls);
|
|
||||||
indexCls(cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void remove(JavaClass cls) {
|
|
||||||
TextSearchIndex index = cache.getTextIndex();
|
|
||||||
if (index == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
indexSet.remove(cls);
|
|
||||||
index.remove(cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIndexNeeded(JavaClass cls) {
|
|
||||||
return !indexSet.contains(cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected static List<StringRef> splitLines(String code) {
|
|
||||||
List<StringRef> lines = StringRef.split(code, ICodeWriter.NL);
|
|
||||||
int size = lines.size();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
lines.set(i, lines.get(i).trim());
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isComplete() {
|
|
||||||
return indexComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComplete(boolean indexComplete) {
|
|
||||||
this.indexComplete = indexComplete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package jadx.gui.jobs;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.JavaClass;
|
|
||||||
import jadx.gui.JadxWrapper;
|
|
||||||
import jadx.gui.ui.MainWindow;
|
|
||||||
import jadx.gui.utils.NLS;
|
|
||||||
|
|
||||||
public class IndexTask implements IBackgroundTask {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(IndexTask.class);
|
|
||||||
|
|
||||||
private final MainWindow mainWindow;
|
|
||||||
private final JadxWrapper wrapper;
|
|
||||||
private final AtomicInteger complete = new AtomicInteger(0);
|
|
||||||
private int expectedCompleteCount;
|
|
||||||
|
|
||||||
private ProcessResult result;
|
|
||||||
|
|
||||||
public IndexTask(MainWindow mainWindow, JadxWrapper wrapper) {
|
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
this.wrapper = wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTitle() {
|
|
||||||
return NLS.str("progress.index");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Runnable> scheduleJobs() {
|
|
||||||
IndexService indexService = mainWindow.getCacheObject().getIndexService();
|
|
||||||
List<JavaClass> classesForIndex = wrapper.getIncludedClasses()
|
|
||||||
.stream()
|
|
||||||
.filter(indexService::isIndexNeeded)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
expectedCompleteCount = classesForIndex.size();
|
|
||||||
|
|
||||||
indexService.setComplete(false);
|
|
||||||
complete.set(0);
|
|
||||||
|
|
||||||
List<Runnable> jobs = new ArrayList<>(2);
|
|
||||||
jobs.add(indexService::indexResources);
|
|
||||||
jobs.add(() -> {
|
|
||||||
for (JavaClass cls : classesForIndex) {
|
|
||||||
try {
|
|
||||||
// TODO: a lot of synchronizations to index object, not efficient for parallel usage
|
|
||||||
if (indexService.indexCls(cls)) {
|
|
||||||
complete.incrementAndGet();
|
|
||||||
} else {
|
|
||||||
LOG.debug("Index skipped for {}", cls);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOG.error("Failed to index class: {}", cls, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDone(ITaskInfo taskInfo) {
|
|
||||||
int skippedCls = expectedCompleteCount - complete.get();
|
|
||||||
if (LOG.isInfoEnabled()) {
|
|
||||||
LOG.info("Index task complete in " + taskInfo.getTime() + " ms"
|
|
||||||
+ ", classes: " + expectedCompleteCount
|
|
||||||
+ ", skipped: " + skippedCls
|
|
||||||
+ ", status: " + taskInfo.getStatus());
|
|
||||||
}
|
|
||||||
IndexService indexService = mainWindow.getCacheObject().getIndexService();
|
|
||||||
indexService.setComplete(true);
|
|
||||||
this.result = new ProcessResult(skippedCls, taskInfo.getStatus(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canBeCanceled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkMemoryUsage() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProcessResult getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple not cancelable task with memory check
|
||||||
|
*/
|
||||||
|
public class SimpleTask implements IBackgroundTask {
|
||||||
|
private final String title;
|
||||||
|
private final List<Runnable> jobs;
|
||||||
|
private final Consumer<TaskStatus> onFinish;
|
||||||
|
|
||||||
|
public SimpleTask(String title, List<Runnable> jobs) {
|
||||||
|
this(title, jobs, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleTask(String title, List<Runnable> jobs, @Nullable Consumer<TaskStatus> onFinish) {
|
||||||
|
this.title = title;
|
||||||
|
this.jobs = jobs;
|
||||||
|
this.onFinish = onFinish;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> scheduleJobs() {
|
||||||
|
return jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish(ITaskInfo taskInfo) {
|
||||||
|
if (onFinish != null) {
|
||||||
|
onFinish.accept(taskInfo.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkMemoryUsage() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeCanceled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCanceled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package jadx.gui.jobs;
|
||||||
|
|
||||||
|
import jadx.gui.utils.UiUtils;
|
||||||
|
|
||||||
|
public class TaskProgress implements ITaskProgress {
|
||||||
|
private int progress;
|
||||||
|
private int total;
|
||||||
|
|
||||||
|
public TaskProgress() {
|
||||||
|
this(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskProgress(long progress, long total) {
|
||||||
|
this(UiUtils.calcProgress(progress, total), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskProgress(int progress, int total) {
|
||||||
|
this.progress = progress;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int progress() {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int total() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProgress(int progress) {
|
||||||
|
this.progress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTotal(int total) {
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,5 +7,5 @@ public enum TaskStatus {
|
|||||||
CANCEL_BY_USER,
|
CANCEL_BY_USER,
|
||||||
CANCEL_BY_TIMEOUT,
|
CANCEL_BY_TIMEOUT,
|
||||||
CANCEL_BY_MEMORY,
|
CANCEL_BY_MEMORY,
|
||||||
ERROR
|
ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user