Merge branch 'master' into rename
This commit is contained in:
@@ -6,5 +6,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.8'
|
||||
id 'com.github.ben-manes.versions' version '0.27.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.26.0"
|
||||
id "com.diffplug.gradle.spotless" version "3.27.1"
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -33,15 +33,15 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.29'
|
||||
compile 'org.slf4j:slf4j-api:1.7.30'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'org.hamcrest:hamcrest-library:2.2'
|
||||
testCompile 'org.mockito:mockito-core:3.1.0'
|
||||
testCompile 'org.assertj:assertj-core:3.14.0'
|
||||
testCompile 'org.mockito:mockito-core:3.2.4'
|
||||
testCompile 'org.assertj:assertj-core:3.15.0'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
|
||||
|
||||
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -85,6 +85,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "what to rename, comma-separated,"
|
||||
@@ -194,6 +197,7 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
@@ -275,6 +279,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationParseKotlinMetadata() {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ dependencies {
|
||||
|
||||
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
|
||||
|
||||
compile 'org.ow2.asm:asm:7.2'
|
||||
compile 'org.ow2.asm:asm:7.3.1'
|
||||
compile 'org.jetbrains:annotations:18.0.0'
|
||||
compile 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
compile 'org.smali:baksmali:2.3.4'
|
||||
compile('org.smali:smali:2.3.4') {
|
||||
compile 'org.smali:baksmali:2.4.0'
|
||||
compile('org.smali:smali:2.4.0') {
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
compile 'com.google.guava:guava:28.1-jre'
|
||||
compile 'com.google.guava:guava:28.2-jre'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.9'
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -49,6 +49,7 @@ public class JadxArgs {
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean deobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
@@ -230,6 +231,14 @@ public class JadxArgs {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public boolean isParseKotlinMetadata() {
|
||||
return parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
|
||||
this.parseKotlinMetadata = parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
@@ -318,6 +327,14 @@ public class JadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||
this.renameFlags = renameFlags;
|
||||
}
|
||||
|
||||
public Set<RenameEnum> getRenameFlags() {
|
||||
return renameFlags;
|
||||
}
|
||||
|
||||
public OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
@@ -355,6 +372,7 @@ public class JadxArgs {
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
|
||||
@@ -47,18 +47,12 @@ public final class JavaMethod implements JavaNode {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> arguments = mth.getArgTypes();
|
||||
if (arguments == null) {
|
||||
arguments = infoArgTypes;
|
||||
}
|
||||
return Utils.collectionMap(arguments,
|
||||
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (retType == null) {
|
||||
retType = mth.getMethodInfo().getReturnType();
|
||||
}
|
||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,26 +11,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
import jadx.core.dex.visitors.DeboxingVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.*;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
@@ -69,6 +50,7 @@ public class Jadx {
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
|
||||
passes.add(new FindSuperUsageVisitor());
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
@@ -83,6 +65,7 @@ public class Jadx {
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
@@ -102,15 +85,16 @@ public class Jadx {
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -12,21 +13,26 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
@@ -34,6 +40,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
import static jadx.core.utils.Utils.isEmpty;
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -45,123 +54,132 @@ public class ClsSet {
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 2;
|
||||
private static final int VERSION = 3;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
||||
private final RootNode root;
|
||||
|
||||
public ClsSet(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
private NClass[] classes;
|
||||
private enum TypeEnum {
|
||||
WILDCARD,
|
||||
GENERIC,
|
||||
GENERIC_TYPE_VARIABLE,
|
||||
OUTER_GENERIC,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
PRIMITIVE
|
||||
}
|
||||
|
||||
private ClspClass[] classes;
|
||||
|
||||
public void loadFromClstFile() throws IOException, DecodeException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Load class set in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFrom(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<>(list.size());
|
||||
Map<String, ClspClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
cls.load();
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setGenerics(cls.getGenerics());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String clsRawName = clsType.getObject();
|
||||
cls.load();
|
||||
ClspClass nClass = new ClspClass(clsType, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
}
|
||||
classes = new NClass[k];
|
||||
classes = new ClspClass[k];
|
||||
k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = getCls(cls.getRawName(), names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls, names));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
ClspClass nClass = getCls(cls, names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<NMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
AccessInfo accessFlags = m.getAccessFlags();
|
||||
if (accessFlags.isPublic() || accessFlags.isProtected()) {
|
||||
processMethodDetails(methods, m, accessFlags);
|
||||
}
|
||||
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<MethodNode> methodsList = cls.getMethods();
|
||||
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
|
||||
for (MethodNode mth : methodsList) {
|
||||
processMethodDetails(mth, methods);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
|
||||
List<ArgType> args = mth.getArgTypes();
|
||||
boolean genericArg = false;
|
||||
ArgType[] genericArgs;
|
||||
if (args.isEmpty()) {
|
||||
genericArgs = null;
|
||||
} else {
|
||||
int argsCount = args.size();
|
||||
genericArgs = new ArgType[argsCount];
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType argType = args.get(i);
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
genericArgs[i] = argType;
|
||||
genericArg = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
||||
retType = null;
|
||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
return;
|
||||
}
|
||||
ArgType genericRetType = mth.getReturnType();
|
||||
boolean varArgs = accessFlags.isVarArgs();
|
||||
if (genericArg || retType != null || varArgs) {
|
||||
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
List<GenericTypeParameter> typeParameters = mth.getTypeParameters();
|
||||
// add only methods with additional info
|
||||
if (varArgs
|
||||
|| notEmpty(throwList)
|
||||
|| notEmpty(typeParameters)
|
||||
|| genericRetType.containsGeneric()
|
||||
|| mth.containsGenericArgs()
|
||||
|| mth.isArgsOverloaded()) {
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
||||
mth.getArgTypes(), genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
if (superClass == null) {
|
||||
// cls is java.lang.Object
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||
parents[0] = superClass;
|
||||
int k = 1;
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
parents[k] = iface;
|
||||
k++;
|
||||
}
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
return parents;
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass cls = names.get(fullName);
|
||||
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||
return getCls(cls.getRawName(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
|
||||
return getCls(clsType.getObject(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
|
||||
ClspClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
@@ -182,16 +200,25 @@ public class ClsSet {
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
boolean clstReplaced = false;
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.getName().equals(clst)) {
|
||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
||||
String entryName = entry.getName();
|
||||
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||
out.putNextEntry(copyEntry);
|
||||
if (entryName.equals(clst)) {
|
||||
save(out);
|
||||
clstReplaced = true;
|
||||
} else {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
if (!clstReplaced) {
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
@@ -203,76 +230,90 @@ public class ClsSet {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
names.put(cls.getName(), cls);
|
||||
for (ClspClass cls : classes) {
|
||||
String clsName = cls.getName();
|
||||
writeString(out, clsName);
|
||||
names.put(clsName, cls);
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
writeGenerics(out, cls, names);
|
||||
List<NMethod> methods = cls.getMethodsList();
|
||||
out.writeByte(methods.size());
|
||||
for (NMethod method : methods) {
|
||||
for (ClspClass cls : classes) {
|
||||
writeArgTypesArray(out, cls.getParents(), names);
|
||||
writeGenericTypeParameters(out, cls.getTypeParameters(), names);
|
||||
List<ClspMethod> methods = cls.getSortedMethodsList();
|
||||
out.writeShort(methods.size());
|
||||
for (ClspMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
int methodsCount = Arrays.stream(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
|
||||
List<GenericInfo> genericsList = cls.getGenerics();
|
||||
out.writeByte(genericsList.size());
|
||||
for (GenericInfo genericInfo : genericsList) {
|
||||
writeArgType(out, genericInfo.getGenericType(), names);
|
||||
List<ArgType> extendsList = genericInfo.getExtendsList();
|
||||
out.writeByte(extendsList.size());
|
||||
for (ArgType type : extendsList) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
|
||||
writeLongString(out, method.getShortId());
|
||||
|
||||
ArgType[] argTypes = method.getGenericArgs();
|
||||
if (argTypes == null) {
|
||||
private static void writeGenericTypeParameters(DataOutputStream out,
|
||||
List<GenericTypeParameter> typeParameters,
|
||||
Map<String, ClspClass> names) throws IOException {
|
||||
if (isEmpty(typeParameters)) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
int argCount = 0;
|
||||
for (ArgType arg : argTypes) {
|
||||
if (arg != null) {
|
||||
argCount++;
|
||||
}
|
||||
}
|
||||
out.writeByte(argCount);
|
||||
// last argument first
|
||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
||||
ArgType argType = argTypes[i];
|
||||
if (argType != null) {
|
||||
out.writeByte(i);
|
||||
writeArgType(out, argType, names);
|
||||
}
|
||||
writeUnsignedByte(out, typeParameters.size());
|
||||
for (GenericTypeParameter typeParameter : typeParameters) {
|
||||
writeArgType(out, typeParameter.getTypeVariable(), names);
|
||||
writeArgTypesList(out, typeParameter.getExtendsList(), names);
|
||||
}
|
||||
}
|
||||
if (method.getReturnType() == null) {
|
||||
out.writeBoolean(false);
|
||||
} else {
|
||||
out.writeBoolean(true);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
}
|
||||
out.writeBoolean(method.isVarArgs());
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
||||
if (argType.getWildcardType() != null) {
|
||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
writeString(out, methodInfo.getName());
|
||||
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
|
||||
writeArgType(out, methodInfo.getReturnType(), names);
|
||||
|
||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
writeGenericTypeParameters(out, method.getTypeParameters(), names);
|
||||
out.writeBoolean(method.isVarArg());
|
||||
writeArgTypesList(out, method.getThrows(), names);
|
||||
}
|
||||
|
||||
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
|
||||
int size = list.size();
|
||||
writeUnsignedByte(out, size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : list) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
|
||||
if (arr == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
int size = arr.length;
|
||||
out.writeByte(size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : arr) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
|
||||
if (argType == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else if (argType.getOuterType() != null) {
|
||||
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
|
||||
writeArgType(out, argType.getOuterType(), names);
|
||||
writeArgType(out, argType.getInnerType(), names);
|
||||
} else if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
ArgType.WildcardBound bound = argType.getWildcardBound();
|
||||
out.writeByte(bound.getNum());
|
||||
@@ -281,28 +322,18 @@ public class ClsSet {
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
ArgType[] types = argType.getGenericTypes();
|
||||
if (types == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(types.length);
|
||||
for (ArgType type : types) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
writeArgTypesArray(out, types, names);
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
@@ -330,7 +361,7 @@ public class ClsSet {
|
||||
}
|
||||
|
||||
private void load(InputStream input) throws IOException, DecodeException {
|
||||
try (DataInputStream in = new DataInputStream(input)) {
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
@@ -339,87 +370,119 @@ public class ClsSet {
|
||||
|| version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int count = in.readInt();
|
||||
classes = new NClass[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
int clsCount = in.readInt();
|
||||
classes = new ClspClass[clsCount];
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
String name = readString(in);
|
||||
classes[i] = new NClass(name, i);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pCount = in.readByte();
|
||||
NClass[] parents = new NClass[pCount];
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
NClass nClass = classes[i];
|
||||
nClass.setParents(parents);
|
||||
nClass.setGenerics(readGenerics(in));
|
||||
nClass.setMethods(readClsMethods(in));
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClspClass nClass = classes[i];
|
||||
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||
nClass.setParents(readArgTypesArray(in));
|
||||
nClass.setTypeParameters(readGenericTypeParameters(in));
|
||||
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
private List<GenericTypeParameter> readGenericTypeParameters(DataInputStream in) throws IOException {
|
||||
int count = readUnsignedByte(in);
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> list = new ArrayList<>(count);
|
||||
List<GenericTypeParameter> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ArgType genericType = readArgType(in);
|
||||
List<ArgType> extendsList;
|
||||
byte extCount = in.readByte();
|
||||
if (extCount == 0) {
|
||||
extendsList = Collections.emptyList();
|
||||
} else {
|
||||
extendsList = new ArrayList<>(extCount);
|
||||
for (int j = 0; j < extCount; j++) {
|
||||
extendsList.add(readArgType(in));
|
||||
}
|
||||
}
|
||||
list.add(new GenericInfo(genericType, extendsList));
|
||||
ArgType typeVariable = readArgType(in);
|
||||
List<ArgType> extendsList = readArgTypesList(in);
|
||||
list.add(new GenericTypeParameter(typeVariable, extendsList));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
|
||||
int mCount = in.readByte();
|
||||
List<NMethod> methods = new ArrayList<>(mCount);
|
||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
int mCount = in.readShort();
|
||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods.add(readMethod(in));
|
||||
methods.add(readMethod(in, clsInfo));
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
ArgType[] argTypes = null;
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
int index = in.readByte();
|
||||
ArgType argType = readArgType(in);
|
||||
if (argTypes == null) {
|
||||
argTypes = new ArgType[index + 1];
|
||||
}
|
||||
argTypes[index] = argType;
|
||||
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
String name = readString(in);
|
||||
List<ArgType> argTypes = readArgTypesList(in);
|
||||
ArgType retType = readArgType(in);
|
||||
List<ArgType> genericArgTypes = readArgTypesList(in);
|
||||
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
|
||||
genericArgTypes = argTypes;
|
||||
}
|
||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
||||
ArgType genericRetType = readArgType(in);
|
||||
if (Objects.equals(genericRetType, retType)) {
|
||||
genericRetType = retType;
|
||||
}
|
||||
List<GenericTypeParameter> typeParameters = readGenericTypeParameters(in);
|
||||
boolean varArgs = in.readBoolean();
|
||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
||||
List<ArgType> throwList = readArgTypesList(in);
|
||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||
return new ClspMethod(methodInfo,
|
||||
genericArgTypes, genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
}
|
||||
|
||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(readArgType(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == -1) {
|
||||
return null;
|
||||
}
|
||||
if (count == 0) {
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
if (ordinal == -1) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= TypeEnum.values().length) {
|
||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||
}
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
int bounds = in.readByte();
|
||||
return bounds == 0
|
||||
? ArgType.wildcard()
|
||||
: ArgType.wildcard(readArgType(in), ArgType.WildcardBound.getByNum(bounds));
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||
return ArgType.WILDCARD;
|
||||
}
|
||||
ArgType objType = readArgType(in);
|
||||
return ArgType.wildcard(objType, bound);
|
||||
|
||||
case OUTER_GENERIC:
|
||||
ArgType outerType = readArgType(in);
|
||||
ArgType innerType = readArgType(in);
|
||||
return ArgType.outerGeneric(outerType, innerType);
|
||||
|
||||
case GENERIC:
|
||||
String obj = classes[in.readInt()].getName();
|
||||
int typeLength = in.readByte();
|
||||
ArgType clsType = classes[in.readInt()].getClsType();
|
||||
int typeLength = readUnsignedByte(in);
|
||||
ArgType[] generics;
|
||||
if (typeLength == 0) {
|
||||
generics = null;
|
||||
@@ -429,13 +492,13 @@ public class ClsSet {
|
||||
generics[i] = readArgType(in);
|
||||
}
|
||||
}
|
||||
return ArgType.generic(obj, generics);
|
||||
return ArgType.generic(clsType, generics);
|
||||
|
||||
case GENERIC_TYPE:
|
||||
case GENERIC_TYPE_VARIABLE:
|
||||
return ArgType.genericType(readString(in));
|
||||
|
||||
case OBJECT:
|
||||
return ArgType.object(classes[in.readInt()].getName());
|
||||
return classes[in.readInt()].getClsType();
|
||||
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
@@ -451,23 +514,16 @@ public class ClsSet {
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeShort(bytes.length);
|
||||
int len = bytes.length;
|
||||
if (len >= 0xFF) {
|
||||
throw new JadxRuntimeException("String is too long: " + name);
|
||||
}
|
||||
writeUnsignedByte(out, bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readLongString(DataInputStream in) throws IOException {
|
||||
int len = in.readShort();
|
||||
int len = readUnsignedByte(in);
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
@@ -485,12 +541,23 @@ public class ClsSet {
|
||||
return new String(bytes, STRING_CHARSET);
|
||||
}
|
||||
|
||||
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
|
||||
if (value < 0 || value >= 0xFF) {
|
||||
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
|
||||
}
|
||||
out.writeByte(value);
|
||||
}
|
||||
|
||||
private static int readUnsignedByte(DataInputStream in) throws IOException {
|
||||
return ((int) in.readByte()) & 0xFF;
|
||||
}
|
||||
|
||||
public int getClassesCount() {
|
||||
return classes.length;
|
||||
}
|
||||
|
||||
public void addToMap(Map<String, NClass> nameMap) {
|
||||
for (NClass cls : classes) {
|
||||
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||
for (ClspClass cls : classes) {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class ClspClass {
|
||||
|
||||
private final ArgType clsType;
|
||||
private final int id;
|
||||
private ArgType[] parents;
|
||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||
private List<GenericTypeParameter> typeParameters = Collections.emptyList();
|
||||
|
||||
public ClspClass(ArgType clsType, int id) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return clsType.getObject();
|
||||
}
|
||||
|
||||
public ArgType getClsType() {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ArgType[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(ArgType[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, ClspMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<ClspMethod> getSortedMethodsList() {
|
||||
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<ClspMethod> methods) {
|
||||
Map<String, ClspMethod> map = new HashMap<>(methods.size());
|
||||
for (ClspMethod mth : methods) {
|
||||
map.put(mth.getMethodInfo().getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public void setTypeParameters(List<GenericTypeParameter> typeParameters) {
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClspClass nClass = (ClspClass) o;
|
||||
return clsType.equals(nClass.clsType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsType.toString();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -26,13 +28,18 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
private Map<String, ClspClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public ClspGraph(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFromClstFile();
|
||||
addClasspath(set);
|
||||
}
|
||||
@@ -51,13 +58,13 @@ public class ClspGraph {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
NClass[] nClasses = new NClass[size];
|
||||
ClspClass[] nClasses = new ClspClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,24 +72,44 @@ public class ClspGraph {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
public NClass getClsDetails(ArgType type) {
|
||||
public ClspClass getClsDetails(ArgType type) {
|
||||
return nameMap.get(type.getObject());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NMethod getMethodDetails(MethodInfo methodInfo) {
|
||||
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
|
||||
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
|
||||
if (clspMethod != null) {
|
||||
return clspMethod;
|
||||
}
|
||||
// deep search
|
||||
for (ArgType parent : cls.getParents()) {
|
||||
ClspClass clspParent = getClspClass(parent);
|
||||
if (clspParent != null) {
|
||||
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
|
||||
if (methodFromParent != null) {
|
||||
return methodFromParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other methods in known ClspClass are 'simple'
|
||||
return new SimpleMethodDetails(methodInfo);
|
||||
}
|
||||
|
||||
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
|
||||
return cls.getMethodsMap().get(methodInfo.getShortId());
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
private ClspClass addClass(ClassNode cls) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String rawName = clsType.getObject();
|
||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||
nameMap.put(rawName, clspClass);
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +134,7 @@ public class ClspGraph {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
ClspClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
@@ -119,15 +146,18 @@ public class ClspGraph {
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
String name = p.getName();
|
||||
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||
for (ArgType p : cls.getParents()) {
|
||||
String name = p.getObject();
|
||||
if (anc.contains(name)) {
|
||||
return name;
|
||||
}
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null) {
|
||||
return r;
|
||||
ClspClass nCls = getClspClass(p);
|
||||
if (nCls != null) {
|
||||
String r = searchCommonParent(anc, nCls);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -138,7 +168,7 @@ public class ClspGraph {
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
ClspClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
@@ -152,15 +182,32 @@ public class ClspGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
private void addAncestorsNames(ClspClass cls, Set<String> result) {
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
addAncestorsNames(parentCls, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClspClass getClspClass(ArgType clsType) {
|
||||
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||
if (clspClass == null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("External class not found: {}", clsType.getObject());
|
||||
}
|
||||
}
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Method node in classpath graph.
|
||||
*/
|
||||
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ArgType returnType;
|
||||
private final List<GenericTypeParameter> typeParameters;
|
||||
private final List<ArgType> throwList;
|
||||
private final boolean varArg;
|
||||
|
||||
public ClspMethod(MethodInfo methodInfo,
|
||||
List<ArgType> argTypes, ArgType returnType,
|
||||
List<GenericTypeParameter> typeParameters,
|
||||
boolean varArgs, List<ArgType> throwList) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.argTypes = argTypes;
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
this.throwList = throwList;
|
||||
this.varArg = varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return throwList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return varArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ClspMethod)) {
|
||||
return false;
|
||||
}
|
||||
ClspMethod other = (ClspMethod) o;
|
||||
return methodInfo.equals(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return methodInfo.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClspMethod other) {
|
||||
return this.methodInfo.compareTo(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ClspMth{");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append("> ");
|
||||
}
|
||||
sb.append(getMethodInfo().getFullName());
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -45,13 +46,15 @@ public class ConvertToClsSet {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
JadxArgs jadxArgs = new JadxArgs();
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
RootNode root = new RootNode(jadxArgs);
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class NClass {
|
||||
|
||||
private final String name;
|
||||
private final int id;
|
||||
private NClass[] parents;
|
||||
private Map<String, NMethod> methodsMap = Collections.emptyMap();
|
||||
private List<GenericInfo> generics = Collections.emptyList();
|
||||
|
||||
public NClass(String name, int id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(NClass[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, NMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<NMethod> getMethodsList() {
|
||||
List<NMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, NMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<NMethod> methods) {
|
||||
Map<String, NMethod> map = new HashMap<>(methods.size());
|
||||
for (NMethod mth : methods) {
|
||||
map.put(mth.getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
public void setGenerics(List<GenericInfo> generics) {
|
||||
this.generics = generics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NClass nClass = (NClass) o;
|
||||
return name.equals(nClass.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Generic method node in classpath graph.
|
||||
*/
|
||||
public class NMethod implements Comparable<NMethod> {
|
||||
|
||||
private final String shortId;
|
||||
|
||||
/**
|
||||
* Array contains only generic args, others set to 'null', size can be less than total args count
|
||||
*/
|
||||
@Nullable
|
||||
private final ArgType[] genericArgs;
|
||||
|
||||
@Nullable
|
||||
private final ArgType retType;
|
||||
|
||||
private final boolean varArgs;
|
||||
|
||||
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
|
||||
this.shortId = shortId;
|
||||
this.genericArgs = genericArgs;
|
||||
this.retType = retType;
|
||||
this.varArgs = varArgs;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType[] getGenericArgs() {
|
||||
return genericArgs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getGenericArg(int i) {
|
||||
ArgType[] args = this.genericArgs;
|
||||
if (args != null && i < args.length) {
|
||||
return args[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof NMethod)) {
|
||||
return false;
|
||||
}
|
||||
NMethod other = (NMethod) o;
|
||||
return shortId.equals(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return shortId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull NMethod other) {
|
||||
return this.shortId.compareTo(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NMethod{'" + shortId + '\''
|
||||
+ ", argTypes=" + Arrays.toString(genericArgs)
|
||||
+ ", retType=" + retType
|
||||
+ ", varArgs=" + varArgs
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class SimpleMethodDetails implements IMethodDetails {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
|
||||
public SimpleMethodDetails(MethodInfo methodInfo) {
|
||||
this.methodInfo = methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return methodInfo.getReturnType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return methodInfo.getArgumentsTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
}
|
||||
}
|
||||
@@ -113,13 +113,11 @@ public class AnnotationGen {
|
||||
return paramName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
if (!throwList.isEmpty()) {
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
||||
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
@@ -149,7 +149,7 @@ public class ClassGen {
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenerics(), true);
|
||||
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
@@ -180,17 +180,17 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
|
||||
public boolean addGenericTypeParameters(CodeWriter code, List<GenericTypeParameter> generics, boolean classDeclaration) {
|
||||
if (generics == null || generics.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (GenericInfo genericInfo : generics) {
|
||||
for (GenericTypeParameter genericInfo : generics) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType type = genericInfo.getGenericType();
|
||||
ArgType type = genericInfo.getTypeVariable();
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
@@ -223,10 +223,25 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
addClassBody(clsCode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clsCode
|
||||
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
||||
* classes)
|
||||
* @throws CodegenException
|
||||
*/
|
||||
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
setBodyGenStarted(true);
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
if (printClassName) {
|
||||
clsCode.startLine();
|
||||
clsCode.add("/* class " + cls.getFullName() + " */");
|
||||
}
|
||||
addFields(clsCode);
|
||||
addInnerClsAndMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
|
||||
@@ -23,7 +23,7 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.FillArrayNode;
|
||||
@@ -53,7 +53,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -607,6 +606,7 @@ public class InsnGen {
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
cls.ensureProcessed();
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
mth.getParentClass().addInlinedClass(cls);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -620,7 +620,7 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
boolean genericCls = cls == null || !cls.getGenerics().isEmpty();
|
||||
boolean genericCls = cls == null || !cls.getGenericTypeParameters().isEmpty();
|
||||
if (argType != null
|
||||
&& argType.getGenericTypes() != null
|
||||
&& genericCls) {
|
||||
@@ -675,7 +675,7 @@ public class InsnGen {
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -760,30 +760,31 @@ public class InsnGen {
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||
@Nullable MethodNode mthNode) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (SkipMethodArgsAttr.isSkip(callMth, i - startArgNum)) {
|
||||
int argOrigPos = i - startArgNum;
|
||||
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
} else {
|
||||
firstArg = false;
|
||||
}
|
||||
boolean cast = addArgCast(code, insn, callMth, arg, i - startArgNum, overloaded);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
@@ -793,91 +794,29 @@ public class InsnGen {
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for method argument.
|
||||
*/
|
||||
private boolean addArgCast(CodeWriter code, InsnNode insn, @Nullable MethodNode callMth,
|
||||
InsnArg arg, int origPos, boolean overloaded) {
|
||||
ArgType castType = null;
|
||||
if (callMth != null) {
|
||||
List<ArgType> argTypes = callMth.getArgTypes();
|
||||
ArgType origType = argTypes.get(origPos);
|
||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
||||
// cancel cast
|
||||
return false;
|
||||
}
|
||||
if (insn instanceof CallMthInterface && origType.containsGenericType()) {
|
||||
ArgType clsType;
|
||||
CallMthInterface mthCall = (CallMthInterface) insn;
|
||||
RegisterArg instanceArg = mthCall.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
clsType = instanceArg.getType();
|
||||
} else {
|
||||
clsType = mthCall.getCallMth().getDeclClass().getType();
|
||||
}
|
||||
ArgType replacedType = TypeUtils.replaceClassGenerics(root, clsType, origType);
|
||||
if (replacedType != null) {
|
||||
castType = replacedType;
|
||||
}
|
||||
if (castType == null) {
|
||||
ArgType invReplType = TypeUtils.replaceMethodGenerics(root, insn, origType);
|
||||
if (invReplType != null) {
|
||||
castType = invReplType;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (castType == null) {
|
||||
castType = origType;
|
||||
}
|
||||
} else {
|
||||
castType = arg.getType();
|
||||
}
|
||||
// TODO: check castType for left type variables
|
||||
|
||||
if (isCastNeeded(arg, castType, overloaded)) {
|
||||
code.add('(');
|
||||
useType(code, castType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCastNeeded(InsnArg arg, ArgType origType, boolean overloaded) {
|
||||
ArgType argType = arg.getType();
|
||||
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0
|
||||
&& (argType.isObject() || argType.isArray())) {
|
||||
return true;
|
||||
}
|
||||
if (argType.equals(origType)) {
|
||||
return false;
|
||||
}
|
||||
return overloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -935,7 +874,7 @@ public class InsnGen {
|
||||
if (parentInsn.contains(AFlag.WRAPPED)) {
|
||||
return false;
|
||||
}
|
||||
return !callMthNode.getReturnType().equals(ArgType.VOID);
|
||||
return !callMthNode.isVoidReturn();
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
|
||||
@@ -98,8 +98,12 @@ public class MethodGen {
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
|
||||
// add 'default' for method with code in interface
|
||||
code.add("default ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
|
||||
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (ai.isConstructor()) {
|
||||
|
||||
@@ -275,37 +275,44 @@ public class RegionGen extends InsnGen {
|
||||
List<Object> keys = caseInfo.getKeys();
|
||||
IContainer c = caseInfo.getContainer();
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null && valueAttr.getValue() != null) {
|
||||
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||
code.startLine("default:");
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
code.startLine("case ");
|
||||
addCaseKey(code, arg, k);
|
||||
code.add(':');
|
||||
}
|
||||
code.add(':');
|
||||
}
|
||||
makeRegionIndent(code, c);
|
||||
}
|
||||
if (sw.getDefaultCase() != null) {
|
||||
code.startLine("default:");
|
||||
makeRegionIndent(code, sw.getDefaultCase());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null) {
|
||||
Object value = valueAttr.getValue();
|
||||
if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
|
||||
code.add(" /*").add(value.toString()).add("*/");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
|
||||
@@ -37,6 +37,9 @@ public class Deobfuscator {
|
||||
|
||||
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
public static final String KOTLIN_METADATA_ANNOTATION = "kotlin.Metadata";
|
||||
public static final String KOTLIN_METADATA_D2_PARAMETER = "d2";
|
||||
public static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)";
|
||||
|
||||
private final JadxArgs args;
|
||||
@NotNull
|
||||
@@ -57,6 +60,7 @@ public class Deobfuscator {
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
private final boolean parseKotlinMetadata;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
@@ -70,6 +74,7 @@ public class Deobfuscator {
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||
this.parseKotlinMetadata = args.isParseKotlinMetadata();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -392,6 +397,18 @@ public class Deobfuscator {
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
|
||||
String metadataClassName = "";
|
||||
String metadataPackageName = "";
|
||||
if (this.parseKotlinMetadata) {
|
||||
String rawClassName = getRawClassNameFromMetadata(cls);
|
||||
if (rawClassName != null) {
|
||||
metadataClassName = rawClassName.substring(rawClassName.lastIndexOf(".") + 1, rawClassName.length() - 1);
|
||||
if (rawClassName.lastIndexOf(".") != -1) {
|
||||
metadataPackageName = rawClassName.substring(1, rawClassName.lastIndexOf("."));
|
||||
}
|
||||
}
|
||||
}
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
@@ -399,14 +416,40 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
||||
if (metadataClassName.isEmpty()) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
||||
} else {
|
||||
alias = metadataClassName;
|
||||
}
|
||||
}
|
||||
PackageNode pkg;
|
||||
if (metadataPackageName.isEmpty()) {
|
||||
pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
} else {
|
||||
pkg = getPackageNode(metadataPackageName, true);
|
||||
}
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getRawClassNameFromMetadata(ClassNode cls) {
|
||||
if (cls.getAnnotation(KOTLIN_METADATA_ANNOTATION) != null
|
||||
&& cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER) != null
|
||||
&& cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER) instanceof List) {
|
||||
Object rawClassNameObject =
|
||||
((List) cls.getAnnotation(KOTLIN_METADATA_ANNOTATION).getValues().get(KOTLIN_METADATA_D2_PARAMETER)).get(0);
|
||||
if (rawClassNameObject instanceof String) {
|
||||
String rawClassName = ((String) rawClassNameObject).trim().replace("/", ".");
|
||||
if (rawClassName.length() > 1 && rawClassName.matches(KOTLIN_METADATA_CLASSNAME_REGEX)) {
|
||||
return rawClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAliasFromSourceFile(ClassNode cls) {
|
||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||
|
||||
@@ -33,6 +33,7 @@ public enum AFlag {
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
SUPER,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
@@ -60,6 +61,7 @@ public enum AFlag {
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
VARARG_CALL,
|
||||
|
||||
/**
|
||||
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
|
||||
|
||||
@@ -24,6 +24,7 @@ import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
@@ -79,6 +80,7 @@ public class AType<T extends IAttribute> {
|
||||
// instruction
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
|
||||
|
||||
// register
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AttrList<T> implements IAttribute {
|
||||
@@ -25,6 +26,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list, "\n");
|
||||
return Utils.listToString(list, CodeWriter.NL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Set;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Storage for different attribute types:
|
||||
@@ -19,6 +20,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class AttributeStorage {
|
||||
|
||||
static {
|
||||
int flagsCount = AFlag.values().length;
|
||||
if (flagsCount >= 64) {
|
||||
throw new JadxRuntimeException("Try to reduce flags count to 64 for use one long in EnumSet, now " + flagsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private final Set<AFlag> flags;
|
||||
private Map<AType<?>, IAttribute> attributes;
|
||||
|
||||
@@ -127,7 +135,7 @@ public class AttributeStorage {
|
||||
list.add(a.toString());
|
||||
}
|
||||
for (IAttribute a : attributes.values()) {
|
||||
list.add(a.toString());
|
||||
list.add(a.toAttrString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -2,4 +2,8 @@ package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
AType<? extends IAttribute> getType();
|
||||
|
||||
default String toAttrString() {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxError implements Comparable<JadxError> {
|
||||
@@ -58,7 +59,7 @@ public class JadxError implements Comparable<JadxError> {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append('\n');
|
||||
str.append(CodeWriter.NL);
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -7,6 +7,8 @@ import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
private final List<LocalVar> localVars;
|
||||
|
||||
@@ -25,6 +27,6 @@ public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||
return "Debug Info:" + NL + " " + Utils.listToString(localVars, NL + " ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
import static com.google.common.base.Ascii.NL;
|
||||
|
||||
public class PhiListAttr implements IAttribute {
|
||||
|
||||
private final List<PhiInsn> list = new LinkedList<>();
|
||||
@@ -28,7 +30,7 @@ public class PhiListAttr implements IAttribute {
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
|
||||
}
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
sb.append(NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
throw new JadxRuntimeException("NO_INDEX for class");
|
||||
}
|
||||
return fromType(dex.root(), dex.getType(clsIndex));
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class InfoStorage {
|
||||
|
||||
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
// use only one MethodInfo instance
|
||||
private final Map<MethodInfo, MethodInfo> uniqueMethods = new HashMap<>();
|
||||
|
||||
public ClassInfo getCls(ArgType type) {
|
||||
return classes.get(type);
|
||||
@@ -23,18 +26,35 @@ public class InfoStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private int generateMethodLookupId(DexNode dex, int mthId) {
|
||||
private static int generateMethodLookupId(DexNode dex, int mthId) {
|
||||
return dex.getDexId() << 16 | mthId;
|
||||
}
|
||||
|
||||
public MethodInfo getMethod(DexNode dex, int mtdId) {
|
||||
return methods.get(generateMethodLookupId(dex, mtdId));
|
||||
synchronized (methods) {
|
||||
return methods.get(generateMethodLookupId(dex, mtdId));
|
||||
}
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo methodInfo) {
|
||||
synchronized (methods) {
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
|
||||
return prev == null ? mth : prev;
|
||||
MethodInfo uniqueMethodInfo = putMethod(methodInfo);
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), uniqueMethodInfo);
|
||||
if (prev != null && prev != uniqueMethodInfo) {
|
||||
throw new JadxRuntimeException("Method lookup id collision: " + methodInfo + ", " + prev + ", " + uniqueMethodInfo);
|
||||
}
|
||||
return uniqueMethodInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(MethodInfo newMth) {
|
||||
synchronized (uniqueMethods) {
|
||||
MethodInfo prev = uniqueMethods.get(newMth);
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
uniqueMethods.put(newMth, newMth);
|
||||
return newMth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
@@ -8,52 +11,48 @@ import com.android.dex.ProtoId;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class MethodInfo {
|
||||
public final class MethodInfo implements Comparable<MethodInfo> {
|
||||
|
||||
private final String name;
|
||||
private final ArgType retType;
|
||||
private final List<ArgType> args;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
retType = dex.getType(proto.getReturnTypeIndex());
|
||||
args = dex.readParamList(proto.getParametersOffset());
|
||||
shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
this.alias = name;
|
||||
this.aliasFromPreset = false;
|
||||
this.declClass = declClass;
|
||||
this.args = args;
|
||||
this.argTypes = args;
|
||||
this.retType = retType;
|
||||
this.shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
return new MethodInfo(declClass, name, args, retType);
|
||||
this.shortId = makeShortId(name, argTypes, retType);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (mth != null) {
|
||||
return mth;
|
||||
MethodInfo storageMth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (storageMth != null) {
|
||||
return storageMth;
|
||||
}
|
||||
mth = new MethodInfo(dex, mthIndex);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
String mthName = dex.getString(mthId.getNameIndex());
|
||||
ClassInfo parentClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
ArgType returnType = dex.getType(proto.getReturnTypeIndex());
|
||||
List<ArgType> args = dex.readParamList(proto.getParametersOffset());
|
||||
MethodInfo newMth = new MethodInfo(parentClass, mthName, args, returnType);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, newMth);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDetails(RootNode rootNode, ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
MethodInfo newMth = new MethodInfo(declClass, name, args, retType);
|
||||
return rootNode.getInfoStorage().putMethod(newMth);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
@@ -61,17 +60,29 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String makeSignature(boolean useAlias, boolean includeRetType) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(useAlias ? alias : name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
return makeShortId(useAlias ? alias : name,
|
||||
argTypes,
|
||||
includeRetType ? retType : null);
|
||||
}
|
||||
|
||||
public static String makeShortId(String name, List<ArgType> argTypes, @Nullable ArgType retType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
sb.append('(');
|
||||
for (ArgType arg : argTypes) {
|
||||
sb.append(TypeGen.signature(arg));
|
||||
}
|
||||
signature.append(')');
|
||||
if (includeRetType) {
|
||||
signature.append(TypeGen.signature(retType));
|
||||
sb.append(')');
|
||||
if (retType != null) {
|
||||
sb.append(TypeGen.signature(retType));
|
||||
}
|
||||
return signature.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isOverloadedBy(MethodInfo otherMthInfo) {
|
||||
return argTypes.size() == otherMthInfo.argTypes.size()
|
||||
&& name.equals(otherMthInfo.name)
|
||||
&& !Objects.equals(this.shortId, otherMthInfo.shortId);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -106,11 +117,11 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public List<ArgType> getArgumentsTypes() {
|
||||
return args;
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return args.size();
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -143,10 +154,7 @@ public final class MethodInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
result = 31 * result + retType.hashCode();
|
||||
result = 31 * result + shortId.hashCode();
|
||||
return result;
|
||||
return shortId.hashCode() + 31 * declClass.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,13 +167,21 @@ public final class MethodInfo {
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
return shortId.equals(other.shortId)
|
||||
&& retType.equals(other.retType)
|
||||
&& declClass.equals(other.declClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MethodInfo other) {
|
||||
int clsCmp = declClass.compareTo(other.declClass);
|
||||
if (clsCmp != 0) {
|
||||
return clsCmp;
|
||||
}
|
||||
return shortId.compareTo(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return declClass.getFullName() + '.' + name
|
||||
+ '(' + Utils.listToString(args) + "):" + retType;
|
||||
+ '(' + Utils.listToString(argTypes) + "):" + retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public abstract class BaseInvokeNode extends InsnNode {
|
||||
public BaseInvokeNode(InsnType type, int argsCount) {
|
||||
super(type, argsCount);
|
||||
}
|
||||
|
||||
public abstract MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
public abstract InsnArg getInstanceArg();
|
||||
|
||||
public abstract boolean isStaticCall();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
public abstract int getFirstArgOffset();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
|
||||
public interface CallMthInterface {
|
||||
|
||||
MethodInfo getCallMth();
|
||||
|
||||
@Nullable
|
||||
RegisterArg getInstanceArg();
|
||||
|
||||
/**
|
||||
* Return offset to match method args from {@link #getCallMth()}
|
||||
*/
|
||||
int getFirstArgOffset();
|
||||
}
|
||||
@@ -623,7 +623,7 @@ public class InsnDecoder {
|
||||
targets[i] = targets[i] - payloadOffset + offset;
|
||||
}
|
||||
int nextOffset = getNextInsnOffset(insnArr, offset);
|
||||
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
|
||||
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset, packed);
|
||||
}
|
||||
|
||||
private InsnNode fillArray(DecodedInstruction insn) {
|
||||
|
||||
@@ -7,11 +7,10 @@ import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
public final class InvokeNode extends BaseInvokeNode {
|
||||
|
||||
private final InvokeType type;
|
||||
private final MethodInfo mth;
|
||||
@@ -55,17 +54,18 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RegisterArg getInstanceArg() {
|
||||
public InsnArg getInstanceArg() {
|
||||
if (type != InvokeType.STATIC && getArgsCount() > 0) {
|
||||
InsnArg firstArg = getArg(0);
|
||||
if (firstArg.isRegister()) {
|
||||
return ((RegisterArg) firstArg);
|
||||
}
|
||||
return getArg(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticCall() {
|
||||
return type == InvokeType.STATIC;
|
||||
}
|
||||
|
||||
public int getFirstArgOffset() {
|
||||
return type == InvokeType.STATIC ? 0 : 1;
|
||||
}
|
||||
@@ -89,6 +89,6 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + mth + " type: " + type;
|
||||
return super.toString() + " type: " + type + " call: " + mth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,22 @@ public class SwitchNode extends TargetInsnNode {
|
||||
private final Object[] keys;
|
||||
private final int[] targets;
|
||||
private final int def; // next instruction
|
||||
private final boolean packed; // type of switch insn, if true can contain filler keys
|
||||
|
||||
private BlockNode[] targetBlocks;
|
||||
private BlockNode defTargetBlock;
|
||||
|
||||
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
|
||||
this(keys, targets, def);
|
||||
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def, boolean packed) {
|
||||
this(keys, targets, def, packed);
|
||||
addArg(arg);
|
||||
}
|
||||
|
||||
private SwitchNode(Object[] keys, int[] targets, int def) {
|
||||
private SwitchNode(Object[] keys, int[] targets, int def, boolean packed) {
|
||||
super(InsnType.SWITCH, 1);
|
||||
this.keys = keys;
|
||||
this.targets = targets;
|
||||
this.def = def;
|
||||
this.packed = packed;
|
||||
}
|
||||
|
||||
public int getCasesCount() {
|
||||
@@ -48,6 +50,10 @@ public class SwitchNode extends TargetInsnNode {
|
||||
return def;
|
||||
}
|
||||
|
||||
public boolean isPacked() {
|
||||
return packed;
|
||||
}
|
||||
|
||||
public BlockNode[] getTargetBlocks() {
|
||||
return targetBlocks;
|
||||
}
|
||||
@@ -103,7 +109,7 @@ public class SwitchNode extends TargetInsnNode {
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
SwitchNode copy = new SwitchNode(keys, targets, def);
|
||||
SwitchNode copy = new SwitchNode(keys, targets, def, packed);
|
||||
copy.targetBlocks = targetBlocks;
|
||||
copy.defTargetBlock = defTargetBlock;
|
||||
return copyCommonParams(copy);
|
||||
@@ -114,9 +120,13 @@ public class SwitchNode extends TargetInsnNode {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(super.toString());
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
sb.append(" case ").append(keys[i])
|
||||
.append(": goto ").append(InsnUtils.formatOffset(targets[i]));
|
||||
sb.append(CodeWriter.NL);
|
||||
sb.append(" case ").append(keys[i]);
|
||||
sb.append(": goto ").append(InsnUtils.formatOffset(targets[i]));
|
||||
}
|
||||
if (def != -1) {
|
||||
sb.append(CodeWriter.NL);
|
||||
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public abstract class ArgType {
|
||||
@@ -24,12 +23,13 @@ public abstract class ArgType {
|
||||
public static final ArgType LONG = primitive(PrimitiveType.LONG);
|
||||
public static final ArgType VOID = primitive(PrimitiveType.VOID);
|
||||
|
||||
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
|
||||
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
|
||||
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||
public static final ArgType OBJECT = objectNoCache(Consts.CLASS_OBJECT);
|
||||
public static final ArgType CLASS = objectNoCache(Consts.CLASS_CLASS);
|
||||
public static final ArgType STRING = objectNoCache(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = objectNoCache(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = objectNoCache(Consts.CLASS_THROWABLE);
|
||||
public static final ArgType OBJECT_ARRAY = array(OBJECT);
|
||||
public static final ArgType WILDCARD = wildcard();
|
||||
|
||||
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
|
||||
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||
@@ -66,10 +66,25 @@ public abstract class ArgType {
|
||||
return new PrimitiveArg(stype);
|
||||
}
|
||||
|
||||
public static ArgType object(String obj) {
|
||||
private static ArgType objectNoCache(String obj) {
|
||||
return new ObjectType(obj);
|
||||
}
|
||||
|
||||
public static ArgType object(String obj) {
|
||||
// TODO: add caching
|
||||
String cleanObjectName = Utils.cleanObjectName(obj);
|
||||
switch (cleanObjectName) {
|
||||
case Consts.CLASS_OBJECT:
|
||||
return OBJECT;
|
||||
case Consts.CLASS_STRING:
|
||||
return STRING;
|
||||
case Consts.CLASS_CLASS:
|
||||
return CLASS;
|
||||
default:
|
||||
return new ObjectType(cleanObjectName);
|
||||
}
|
||||
}
|
||||
|
||||
public static ArgType genericType(String type) {
|
||||
return new GenericType(type);
|
||||
}
|
||||
@@ -82,12 +97,16 @@ public abstract class ArgType {
|
||||
return new WildcardType(obj, bound);
|
||||
}
|
||||
|
||||
public static ArgType parseGenericSignature(String sign) {
|
||||
return new SignatureParser(sign).consumeType();
|
||||
public static ArgType generic(ArgType obj, ArgType... generics) {
|
||||
if (!obj.isObject()) {
|
||||
throw new IllegalArgumentException("Expected Object as ArgType, got: " + obj);
|
||||
}
|
||||
return new GenericObject(obj.getObject(), generics);
|
||||
}
|
||||
|
||||
public static ArgType generic(String obj, ArgType... generics) {
|
||||
return new GenericObject(obj, generics);
|
||||
String cleanObjectName = Utils.cleanObjectName(obj);
|
||||
return new GenericObject(cleanObjectName, generics);
|
||||
}
|
||||
|
||||
public static ArgType outerGeneric(ArgType genericOuterType, ArgType innerType) {
|
||||
@@ -160,7 +179,7 @@ public abstract class ArgType {
|
||||
protected final String objName;
|
||||
|
||||
public ObjectType(String obj) {
|
||||
this.objName = Utils.cleanObjectName(obj);
|
||||
this.objName = obj;
|
||||
this.hash = objName.hashCode();
|
||||
}
|
||||
|
||||
@@ -555,12 +574,12 @@ public abstract class ArgType {
|
||||
|
||||
public abstract PrimitiveType[] getPossibleTypes();
|
||||
|
||||
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
||||
public static boolean isCastNeeded(RootNode root, ArgType from, ArgType to) {
|
||||
if (from.equals(to)) {
|
||||
return false;
|
||||
}
|
||||
if (from.isObject() && to.isObject()
|
||||
&& dex.root().getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||
&& root.getClsp().isImplements(from.getObject(), to.getObject())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -679,25 +698,44 @@ public abstract class ArgType {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public boolean containsGenericType() {
|
||||
public boolean containsGeneric() {
|
||||
if (isGeneric() || isGenericType()) {
|
||||
return true;
|
||||
}
|
||||
if (isArray()) {
|
||||
ArgType arrayElement = getArrayElement();
|
||||
if (arrayElement != null) {
|
||||
return arrayElement.containsGeneric();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsTypeVariable() {
|
||||
if (isGenericType()) {
|
||||
return true;
|
||||
}
|
||||
ArgType wildcardType = getWildcardType();
|
||||
if (wildcardType != null) {
|
||||
return wildcardType.containsGenericType();
|
||||
return wildcardType.containsTypeVariable();
|
||||
}
|
||||
if (isGeneric()) {
|
||||
ArgType[] genericTypes = getGenericTypes();
|
||||
if (genericTypes != null) {
|
||||
for (ArgType genericType : genericTypes) {
|
||||
if (genericType.containsGenericType()) {
|
||||
if (genericType.containsTypeVariable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (isArray()) {
|
||||
ArgType arrayElement = getArrayElement();
|
||||
if (arrayElement != null) {
|
||||
return arrayElement.containsTypeVariable();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public abstract class InsnArg extends Typed {
|
||||
}
|
||||
|
||||
private static InsnWrapArg wrap(InsnNode insn) {
|
||||
insn.add(AFlag.WRAPPED);
|
||||
return new InsnWrapArg(insn);
|
||||
}
|
||||
|
||||
@@ -140,9 +141,17 @@ public abstract class InsnArg extends Typed {
|
||||
InsnArg arg;
|
||||
InsnType type = insn.getType();
|
||||
if (type == InsnType.CONST || type == InsnType.MOVE) {
|
||||
arg = insn.getArg(0);
|
||||
insn.add(AFlag.REMOVE);
|
||||
insn.add(AFlag.DONT_GENERATE);
|
||||
if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
RegisterArg resArg = insn.getResult();
|
||||
arg = wrap(insn);
|
||||
if (resArg != null) {
|
||||
arg.setType(resArg.getType());
|
||||
}
|
||||
} else {
|
||||
arg = insn.getArg(0);
|
||||
insn.add(AFlag.REMOVE);
|
||||
insn.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
} else {
|
||||
arg = wrapArg(insn);
|
||||
}
|
||||
@@ -156,8 +165,6 @@ public abstract class InsnArg extends Typed {
|
||||
public static InsnArg wrapArg(InsnNode insn) {
|
||||
RegisterArg resArg = insn.getResult();
|
||||
InsnArg arg = wrap(insn);
|
||||
insn.add(AFlag.WRAPPED);
|
||||
|
||||
switch (insn.getType()) {
|
||||
case CONST:
|
||||
case MOVE:
|
||||
|
||||
@@ -11,6 +11,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegisterArg extends InsnArg implements Named {
|
||||
public static final String THIS_ARG_NAME = "this";
|
||||
public static final String SUPER_ARG_NAME = "super";
|
||||
|
||||
protected final int regNum;
|
||||
// not null after SSATransform pass
|
||||
@@ -87,6 +88,9 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (isSuper()) {
|
||||
return SUPER_ARG_NAME;
|
||||
}
|
||||
if (isThis()) {
|
||||
return THIS_ARG_NAME;
|
||||
}
|
||||
@@ -96,6 +100,10 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return sVar.getName();
|
||||
}
|
||||
|
||||
private boolean isSuper() {
|
||||
return contains(AFlag.SUPER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
if (sVar != null && name != null) {
|
||||
|
||||
@@ -4,14 +4,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public final class ConstructorInsn extends InsnNode implements CallMthInterface {
|
||||
public final class ConstructorInsn extends BaseInvokeNode {
|
||||
|
||||
private final MethodInfo callMth;
|
||||
private final CallType callType;
|
||||
@@ -59,6 +59,7 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
|
||||
this.callType = callType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getCallMth() {
|
||||
return callMth;
|
||||
}
|
||||
@@ -93,6 +94,11 @@ public final class ConstructorInsn extends InsnNode implements CallMthInterface
|
||||
return callType == CallType.SELF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticCall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstArgOffset() {
|
||||
return 0;
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -54,12 +56,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private AccessInfo accessFlags;
|
||||
private ArgType superClass;
|
||||
private List<ArgType> interfaces;
|
||||
private List<GenericInfo> generics = Collections.emptyList();
|
||||
private List<GenericTypeParameter> generics = Collections.emptyList();
|
||||
|
||||
private List<MethodNode> methods;
|
||||
private List<FieldNode> fields;
|
||||
private List<ClassNode> innerClasses = Collections.emptyList();
|
||||
|
||||
private List<ClassNode> inlinedClasses = Collections.emptyList();
|
||||
|
||||
// store smali
|
||||
private String smali;
|
||||
// store parent for inner classes or 'this' otherwise
|
||||
@@ -83,6 +87,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
try {
|
||||
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
|
||||
this.superClass = null;
|
||||
// only java.lang.Object don't have super class
|
||||
if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
|
||||
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
|
||||
}
|
||||
} else {
|
||||
this.superClass = dex.getType(cls.getSupertypeIndex());
|
||||
}
|
||||
@@ -178,6 +186,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
|
||||
for (FieldNode f : staticFields) {
|
||||
if (f.getAccessFlags().isFinal()) {
|
||||
// incorrect initialization will be removed if assign found in constructor
|
||||
f.addAttr(FieldInitAttr.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +209,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
try {
|
||||
// parse class generic map
|
||||
generics = sp.consumeGenericMap();
|
||||
generics = sp.consumeGenericTypeParameters();
|
||||
// parse super class signature
|
||||
superClass = sp.consumeType();
|
||||
// parse interfaces signatures
|
||||
@@ -360,7 +369,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
public List<GenericTypeParameter> getGenericTypeParameters() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
@@ -481,6 +490,24 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all inner and inlined classes recursively
|
||||
*
|
||||
* @param resultClassesSet all identified inner and inlined classes are added to this set
|
||||
*/
|
||||
public void getInnerAndInlinedClassesRecursive(Set<ClassNode> resultClassesSet) {
|
||||
for (ClassNode innerCls : innerClasses) {
|
||||
if (resultClassesSet.add(innerCls)) {
|
||||
innerCls.getInnerAndInlinedClassesRecursive(resultClassesSet);
|
||||
}
|
||||
}
|
||||
for (ClassNode inlinedCls : inlinedClasses) {
|
||||
if (resultClassesSet.add(inlinedCls)) {
|
||||
inlinedCls.getInnerAndInlinedClassesRecursive(resultClassesSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInnerClass(ClassNode cls) {
|
||||
if (innerClasses.isEmpty()) {
|
||||
innerClasses = new ArrayList<>(5);
|
||||
@@ -489,6 +516,13 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
cls.parentClass = this;
|
||||
}
|
||||
|
||||
public void addInlinedClass(ClassNode cls) {
|
||||
if (inlinedClasses.isEmpty()) {
|
||||
inlinedClasses = new ArrayList<>(5);
|
||||
}
|
||||
inlinedClasses.add(cls);
|
||||
}
|
||||
|
||||
public boolean isEnum() {
|
||||
return getAccessFlags().isEnum()
|
||||
&& getSuperClass() != null
|
||||
@@ -567,7 +601,9 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
StringWriter stringWriter = new StringWriter(4096);
|
||||
getSmali(this, stringWriter);
|
||||
stringWriter.append(System.lineSeparator());
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
|
||||
getInnerAndInlinedClassesRecursive(allInlinedClasses);
|
||||
for (ClassNode innerClass : allInlinedClasses) {
|
||||
getSmali(innerClass, stringWriter);
|
||||
stringWriter.append(System.lineSeparator());
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class GenericInfo {
|
||||
private final ArgType genericType;
|
||||
private final List<ArgType> extendsList;
|
||||
|
||||
public GenericInfo(ArgType genericType, List<ArgType> extendsList) {
|
||||
this.genericType = genericType;
|
||||
this.extendsList = extendsList;
|
||||
}
|
||||
|
||||
public ArgType getGenericType() {
|
||||
return genericType;
|
||||
}
|
||||
|
||||
public List<ArgType> getExtendsList() {
|
||||
return extendsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GenericInfo other = (GenericInfo) o;
|
||||
return genericType.equals(other.genericType)
|
||||
&& extendsList.equals(other.extendsList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * genericType.hashCode() + extendsList.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GenericInfo{" + genericType + " extends: " + extendsList + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
public class GenericTypeParameter {
|
||||
private final ArgType typeVariable;
|
||||
private final List<ArgType> extendsList;
|
||||
|
||||
public GenericTypeParameter(ArgType typeVariable, List<ArgType> extendsList) {
|
||||
this.typeVariable = typeVariable;
|
||||
this.extendsList = extendsList;
|
||||
}
|
||||
|
||||
public ArgType getTypeVariable() {
|
||||
return typeVariable;
|
||||
}
|
||||
|
||||
public List<ArgType> getExtendsList() {
|
||||
return extendsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GenericTypeParameter other = (GenericTypeParameter) o;
|
||||
return typeVariable.equals(other.typeVariable)
|
||||
&& extendsList.equals(other.extendsList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * typeVariable.hashCode() + extendsList.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(typeVariable);
|
||||
if (notEmpty(extendsList)) {
|
||||
sb.append(" extends ");
|
||||
sb.append(Utils.listToString(extendsList, " & "));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public interface IMethodDetails extends IAttribute {
|
||||
|
||||
MethodInfo getMethodInfo();
|
||||
|
||||
ArgType getReturnType();
|
||||
|
||||
List<ArgType> getArgTypes();
|
||||
|
||||
List<GenericTypeParameter> getTypeParameters();
|
||||
|
||||
List<ArgType> getThrows();
|
||||
|
||||
boolean isVarArg();
|
||||
|
||||
@Override
|
||||
default AType<IMethodDetails> getType() {
|
||||
return AType.METHOD_DETAILS;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String toAttrString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MD:");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append(">:");
|
||||
}
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -404,13 +405,13 @@ public class InsnNode extends LineAttrNode {
|
||||
|
||||
protected void appendArgs(StringBuilder sb) {
|
||||
String argsStr = Utils.listToString(arguments);
|
||||
if (argsStr.length() < 60) {
|
||||
if (argsStr.length() < 120) {
|
||||
sb.append(argsStr);
|
||||
} else {
|
||||
// wrap args
|
||||
String separator = "\n ";
|
||||
String separator = CodeWriter.NL + " ";
|
||||
sb.append(separator).append(Utils.listToString(arguments, separator));
|
||||
sb.append('\n');
|
||||
sb.append(CodeWriter.NL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -16,8 +17,10 @@ import com.android.dex.Code;
|
||||
import com.android.dex.Code.CatchHandler;
|
||||
import com.android.dex.Code.Try;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
@@ -46,7 +49,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadable, ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@@ -66,7 +69,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
// additional info available after load, keep on unload
|
||||
private ArgType retType;
|
||||
private List<ArgType> argTypes;
|
||||
private List<GenericInfo> generics;
|
||||
private List<GenericTypeParameter> typeParameters;
|
||||
|
||||
// decompilation data, reset on unload
|
||||
private RegisterArg thisArg;
|
||||
@@ -96,7 +99,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
if (noCode) {
|
||||
return;
|
||||
}
|
||||
// don't unload retType, argTypes, generics
|
||||
// don't unload retType, argTypes, typeParameters
|
||||
thisArg = null;
|
||||
argsList = null;
|
||||
sVars = Collections.emptyList();
|
||||
@@ -199,7 +202,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
this.generics = sp.consumeGenericMap();
|
||||
this.typeParameters = sp.consumeGenericTypeParameters();
|
||||
List<ArgType> argsTypes = sp.consumeMethodArgs();
|
||||
this.retType = sp.consumeType();
|
||||
|
||||
@@ -217,7 +220,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
return argsTypes;
|
||||
} catch (Exception e) {
|
||||
addWarningComment("Failed to parse method signature: " + sp.getSignature(), e);
|
||||
addWarnComment("Failed to parse method signature: " + sp.getSignature(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -273,17 +276,32 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<ArgType> getArgTypes() {
|
||||
if (argTypes == null) {
|
||||
throw new JadxRuntimeException("Method types not initialized: " + this);
|
||||
throw new JadxRuntimeException("Method generic types not initialized: " + this);
|
||||
}
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(mthInfo.getArgumentsTypes(), getArgTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVoidReturn() {
|
||||
return mthInfo.getReturnType().equals(ArgType.VOID);
|
||||
}
|
||||
|
||||
public List<RegisterArg> getArgRegs() {
|
||||
if (argsList == null) {
|
||||
throw new JadxRuntimeException("Method args not loaded: " + this
|
||||
throw new JadxRuntimeException("Method arg registers not loaded: " + this
|
||||
+ ", class status: " + parentClass.getTopParentClass().getState());
|
||||
}
|
||||
return argsList;
|
||||
@@ -309,12 +327,9 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
this.add(AFlag.SKIP_FIRST_ARG);
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public List<GenericInfo> getGenerics() {
|
||||
return generics;
|
||||
@Override
|
||||
public List<GenericTypeParameter> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
|
||||
@@ -584,25 +599,31 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return exceptionHandlers.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<ArgType> getThrows() {
|
||||
Annotation an = getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (List<ArgType>) an.getDefaultValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if exists method with same name and arguments count
|
||||
*/
|
||||
public boolean isArgsOverload() {
|
||||
int argsCount = mthInfo.getArgumentsTypes().size();
|
||||
if (argsCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = getName();
|
||||
public boolean isArgsOverloaded() {
|
||||
MethodInfo thisMthInfo = this.mthInfo;
|
||||
// quick check in current class
|
||||
for (MethodNode method : parentClass.getMethods()) {
|
||||
MethodInfo otherMthInfo = method.mthInfo;
|
||||
if (this != method
|
||||
&& otherMthInfo.getArgumentsTypes().size() == argsCount
|
||||
&& otherMthInfo.getName().equals(name)) {
|
||||
if (method == this) {
|
||||
continue;
|
||||
}
|
||||
if (method.getMethodInfo().isOverloadedBy(thisMthInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return root().getMethodUtils().isMethodArgsOverloaded(parentClass.getClassInfo().getType(), thisMthInfo);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -707,11 +728,11 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
ErrorsCounter.methodWarn(this, warnStr);
|
||||
}
|
||||
|
||||
public void addWarningComment(String warn) {
|
||||
addWarningComment(warn, null);
|
||||
public void addWarnComment(String warn) {
|
||||
addWarnComment(warn, null);
|
||||
}
|
||||
|
||||
public void addWarningComment(String warn, @Nullable Throwable exc) {
|
||||
public void addWarnComment(String warn, @Nullable Throwable exc) {
|
||||
String commentStr = "JADX WARN: " + warn;
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
if (exc != null) {
|
||||
@@ -730,6 +751,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
ErrorsCounter.methodError(this, errStr, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return mthInfo;
|
||||
}
|
||||
@@ -752,6 +774,11 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return accFlags.isVarArgs();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
@@ -776,7 +803,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClass + "." + mthInfo.getName()
|
||||
+ '(' + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
|
||||
+ '(' + Utils.listToString(argTypes) + "):"
|
||||
+ retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.clsp.NClass;
|
||||
import jadx.core.clsp.NMethod;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.nodes.utils.TypeUtils;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.CacheStorage;
|
||||
@@ -48,6 +48,8 @@ public class RootNode {
|
||||
private final InfoStorage infoStorage = new InfoStorage();
|
||||
private final CacheStorage cacheStorage = new CacheStorage();
|
||||
private final TypeUpdate typeUpdate;
|
||||
private final MethodUtils methodUtils;
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
private final ICodeCache codeCache;
|
||||
|
||||
@@ -65,6 +67,10 @@ public class RootNode {
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
this.codeCache = args.getCodeCache();
|
||||
this.methodUtils = new MethodUtils(this);
|
||||
this.typeUtils = new TypeUtils(this);
|
||||
|
||||
this.dexNodes = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void load(List<InputFile> inputFiles) {
|
||||
@@ -119,7 +125,7 @@ public class RootNode {
|
||||
public void initClassPath() {
|
||||
try {
|
||||
if (this.clsp == null) {
|
||||
ClspGraph newClsp = new ClspGraph();
|
||||
ClspGraph newClsp = new ClspGraph(this);
|
||||
newClsp.load();
|
||||
|
||||
List<ClassNode> classes = new ArrayList<>();
|
||||
@@ -168,6 +174,26 @@ public class RootNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassNode resolveClass(ArgType clsType) {
|
||||
if (!clsType.isTypeKnown() || clsType.isGenericType()) {
|
||||
return null;
|
||||
}
|
||||
if (clsType.getWildcardBound() == ArgType.WildcardBound.UNBOUND) {
|
||||
return null;
|
||||
}
|
||||
if (clsType.isGeneric()) {
|
||||
clsType = ArgType.object(clsType.getObject());
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
ClassNode cls = dexNode.resolveClass(clsType);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassNode searchClassByName(String fullName) {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, fullName);
|
||||
@@ -206,6 +232,10 @@ public class RootNode {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
MethodNode methodNode = cls.searchMethod(mth);
|
||||
if (methodNode != null) {
|
||||
return methodNode;
|
||||
}
|
||||
return cls.dex().deepResolveMethod(cls, mth.makeSignature(false));
|
||||
}
|
||||
|
||||
@@ -232,60 +262,6 @@ public class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getMethodGenericReturnType(MethodInfo callMth) {
|
||||
MethodNode methodNode = deepResolveMethod(callMth);
|
||||
if (methodNode != null) {
|
||||
ArgType returnType = methodNode.getReturnType();
|
||||
if (returnType != null && (returnType.isGeneric() || returnType.isGenericType())) {
|
||||
return returnType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
NMethod methodDetails = clsp.getMethodDetails(callMth);
|
||||
if (methodDetails != null) {
|
||||
return methodDetails.getReturnType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ArgType> getMethodArgTypes(MethodInfo callMth) {
|
||||
MethodNode methodNode = deepResolveMethod(callMth);
|
||||
if (methodNode != null) {
|
||||
return methodNode.getArgTypes();
|
||||
}
|
||||
NMethod methodDetails = clsp.getMethodDetails(callMth);
|
||||
if (methodDetails != null && methodDetails.getGenericArgs() != null) {
|
||||
List<ArgType> argTypes = callMth.getArgumentsTypes();
|
||||
int argsCount = argTypes.size();
|
||||
List<ArgType> list = new ArrayList<>(argsCount);
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType genericArgType = methodDetails.getGenericArg(i);
|
||||
if (genericArgType != null) {
|
||||
list.add(genericArgType);
|
||||
} else {
|
||||
list.add(argTypes.get(i));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GenericInfo> getClassGenerics(ArgType type) {
|
||||
ClassNode classNode = resolveClass(ClassInfo.fromType(this, type));
|
||||
if (classNode != null) {
|
||||
return classNode.getGenerics();
|
||||
}
|
||||
NClass clsDetails = getClsp().getClsDetails(type);
|
||||
if (clsDetails == null || clsDetails.getGenerics().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> generics = clsDetails.getGenerics();
|
||||
return generics == null ? Collections.emptyList() : generics;
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
@@ -335,4 +311,11 @@ public class RootNode {
|
||||
return codeCache;
|
||||
}
|
||||
|
||||
public MethodUtils getMethodUtils() {
|
||||
return methodUtils;
|
||||
}
|
||||
|
||||
public TypeUtils getTypeUtils() {
|
||||
return typeUtils;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -12,7 +13,7 @@ import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SignatureParser {
|
||||
@@ -65,32 +66,40 @@ public class SignatureParser {
|
||||
* @return string from 'mark' to current position (not including current character)
|
||||
*/
|
||||
private String slice() {
|
||||
if (mark >= pos) {
|
||||
int start = mark == -1 ? 0 : mark;
|
||||
if (start >= pos) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos);
|
||||
return sign.substring(start, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive slice (includes current character)
|
||||
*/
|
||||
private String inclusiveSlice() {
|
||||
if (mark >= pos) {
|
||||
int start = mark;
|
||||
if (start == -1) {
|
||||
start = 0;
|
||||
}
|
||||
int last = pos + 1;
|
||||
if (start >= last) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos + 1);
|
||||
return sign.substring(start, last);
|
||||
}
|
||||
|
||||
private boolean forwardTo(char lastChar) {
|
||||
private boolean skipUntil(char untilChar) {
|
||||
int startPos = pos;
|
||||
char ch;
|
||||
while ((ch = next()) != STOP_CHAR) {
|
||||
if (ch == lastChar) {
|
||||
while (true) {
|
||||
if (lookAhead(untilChar)) {
|
||||
return true;
|
||||
}
|
||||
char ch = next();
|
||||
if (ch == STOP_CHAR) {
|
||||
pos = startPos;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pos = startPos;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void consume(char exp) {
|
||||
@@ -109,14 +118,14 @@ public class SignatureParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String consumeUntil(char lastChar) {
|
||||
@Nullable
|
||||
public String consumeUntil(char lastChar) {
|
||||
mark();
|
||||
return forwardTo(lastChar) ? slice() : null;
|
||||
return skipUntil(lastChar) ? inclusiveSlice() : null;
|
||||
}
|
||||
|
||||
public ArgType consumeType() {
|
||||
char ch = next();
|
||||
mark();
|
||||
switch (ch) {
|
||||
case 'L':
|
||||
ArgType obj = consumeObjectType(false);
|
||||
@@ -127,10 +136,13 @@ public class SignatureParser {
|
||||
case 'T':
|
||||
next();
|
||||
mark();
|
||||
if (forwardTo(';')) {
|
||||
return ArgType.genericType(slice());
|
||||
String typeVarName = consumeUntil(';');
|
||||
if (typeVarName != null) {
|
||||
consume(';');
|
||||
return ArgType.genericType(typeVarName);
|
||||
}
|
||||
break;
|
||||
|
||||
case '[':
|
||||
return ArgType.array(consumeType());
|
||||
|
||||
@@ -145,7 +157,7 @@ public class SignatureParser {
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new JadxRuntimeException("Can't parse type: " + debugString());
|
||||
throw new JadxRuntimeException("Can't parse type: " + debugString() + ", unexpected: " + ch);
|
||||
}
|
||||
|
||||
private ArgType consumeObjectType(boolean incompleteType) {
|
||||
@@ -220,11 +232,11 @@ public class SignatureParser {
|
||||
* <p/>
|
||||
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
||||
*/
|
||||
public List<GenericInfo> consumeGenericMap() {
|
||||
public List<GenericTypeParameter> consumeGenericTypeParameters() {
|
||||
if (!lookAhead('<')) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericInfo> list = new ArrayList<>();
|
||||
List<GenericTypeParameter> list = new ArrayList<>();
|
||||
consume('<');
|
||||
while (true) {
|
||||
if (lookAhead('>') || next() == STOP_CHAR) {
|
||||
@@ -232,12 +244,13 @@ public class SignatureParser {
|
||||
}
|
||||
String id = consumeUntil(':');
|
||||
if (id == null) {
|
||||
LOG.error("Failed to parse generic map: {}", sign);
|
||||
LOG.error("Failed to parse generic types map: {}", sign);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
consume(':');
|
||||
tryConsume(':');
|
||||
List<ArgType> types = consumeExtendsTypesList();
|
||||
list.add(new GenericInfo(ArgType.genericType(id), types));
|
||||
list.add(new GenericTypeParameter(ArgType.genericType(id), types));
|
||||
}
|
||||
consume('>');
|
||||
return list;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package jadx.core.dex.nodes.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspMethod;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class MethodUtils {
|
||||
private final RootNode root;
|
||||
|
||||
public MethodUtils(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = invokeNode.get(AType.METHOD_DETAILS);
|
||||
if (methodDetails != null) {
|
||||
return methodDetails;
|
||||
}
|
||||
return getMethodDetails(invokeNode.getCallMth());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(MethodInfo callMth) {
|
||||
MethodNode mthNode = root.deepResolveMethod(callMth);
|
||||
if (mthNode != null) {
|
||||
return mthNode;
|
||||
}
|
||||
return root.getClsp().getMethodDetails(callMth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()}
|
||||
*/
|
||||
public boolean isMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo) {
|
||||
return processMethodArgsOverloaded(startCls, mthInfo, null);
|
||||
}
|
||||
|
||||
public List<IMethodDetails> collectOverloadedMethods(ArgType startCls, MethodInfo mthInfo) {
|
||||
List<IMethodDetails> list = new ArrayList<>();
|
||||
processMethodArgsOverloaded(startCls, mthInfo, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getMethodGenericReturnType(BaseInvokeNode invokeNode) {
|
||||
IMethodDetails methodDetails = getMethodDetails(invokeNode);
|
||||
if (methodDetails != null) {
|
||||
ArgType returnType = methodDetails.getReturnType();
|
||||
if (returnType != null && returnType.containsGeneric()) {
|
||||
return returnType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
|
||||
if (startCls == null || !startCls.isObject()) {
|
||||
return false;
|
||||
}
|
||||
boolean isMthConstructor = mthInfo.isConstructor() || mthInfo.isClassInit();
|
||||
ClassNode classNode = root.resolveClass(startCls);
|
||||
if (classNode != null) {
|
||||
for (MethodNode mth : classNode.getMethods()) {
|
||||
if (mthInfo.isOverloadedBy(mth.getMethodInfo())) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
collectedMths.add(mth);
|
||||
}
|
||||
}
|
||||
if (!isMthConstructor) {
|
||||
if (processMethodArgsOverloaded(classNode.getSuperClass(), mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (ArgType parentInterface : classNode.getInterfaces()) {
|
||||
if (processMethodArgsOverloaded(parentInterface, mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ClspClass clsDetails = root.getClsp().getClsDetails(startCls);
|
||||
if (clsDetails == null) {
|
||||
// class info not available
|
||||
return false;
|
||||
}
|
||||
for (ClspMethod clspMth : clsDetails.getMethodsMap().values()) {
|
||||
if (mthInfo.isOverloadedBy(clspMth.getMethodInfo())) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
collectedMths.add(clspMth);
|
||||
}
|
||||
}
|
||||
if (!isMthConstructor) {
|
||||
for (ArgType parent : clsDetails.getParents()) {
|
||||
if (processMethodArgsOverloaded(parent, mthInfo, collectedMths)) {
|
||||
if (collectedMths == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package jadx.core.dex.nodes.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class TypeUtils {
|
||||
private final RootNode root;
|
||||
|
||||
public TypeUtils(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GenericTypeParameter> getClassGenerics(ArgType type) {
|
||||
ClassNode classNode = root.resolveClass(type);
|
||||
if (classNode != null) {
|
||||
return classNode.getGenericTypeParameters();
|
||||
}
|
||||
ClspClass clsDetails = root.getClsp().getClsDetails(type);
|
||||
if (clsDetails == null || clsDetails.getTypeParameters().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<GenericTypeParameter> generics = clsDetails.getTypeParameters();
|
||||
return generics == null ? Collections.emptyList() : generics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace generic types in {@code typeWithGeneric} using instance types
|
||||
* <br>
|
||||
* Example:
|
||||
* <ul>
|
||||
* <li>{@code instanceType: Set<String>}
|
||||
* <li>{@code typeWithGeneric: Iterator<E>}
|
||||
* <li>{@code return: Iterator<String>}
|
||||
* </ul>
|
||||
*/
|
||||
@Nullable
|
||||
public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric != null) {
|
||||
Map<ArgType, ArgType> replaceMap = getTypeVariablesMapping(instanceType);
|
||||
if (!replaceMap.isEmpty()) {
|
||||
return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<ArgType, ArgType> getTypeVariablesMapping(ArgType clsType) {
|
||||
if (!clsType.isGeneric()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
List<GenericTypeParameter> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
|
||||
if (typeParameters.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
ArgType[] actualTypes = clsType.getGenericTypes();
|
||||
if (actualTypes == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
int genericParamsCount = actualTypes.length;
|
||||
if (genericParamsCount != typeParameters.size()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<ArgType, ArgType> replaceMap = new HashMap<>(genericParamsCount);
|
||||
for (int i = 0; i < genericParamsCount; i++) {
|
||||
ArgType actualType = actualTypes[i];
|
||||
ArgType genericType = typeParameters.get(i).getTypeVariable();
|
||||
replaceMap.put(genericType, actualType);
|
||||
}
|
||||
return replaceMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType replaceMethodGenerics(BaseInvokeNode invokeInsn, IMethodDetails details, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
List<ArgType> methodArgTypes = details.getArgTypes();
|
||||
if (methodArgTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int firstArgOffset = invokeInsn.getFirstArgOffset();
|
||||
int argsCount = methodArgTypes.size();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType methodArgType = methodArgTypes.get(i);
|
||||
InsnArg insnArg = invokeInsn.getArg(i + firstArgOffset);
|
||||
ArgType insnType = insnArg.getType();
|
||||
if (methodArgType.equals(typeWithGeneric)) {
|
||||
return insnType;
|
||||
}
|
||||
}
|
||||
// TODO build complete map for type variables
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType replaceTypeVariablesUsingMap(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
|
||||
if (replaceType.isGenericType()) {
|
||||
return replaceMap.get(replaceType);
|
||||
}
|
||||
|
||||
ArgType wildcardType = replaceType.getWildcardType();
|
||||
if (wildcardType != null && wildcardType.containsTypeVariable()) {
|
||||
ArgType newWildcardType = replaceTypeVariablesUsingMap(wildcardType, replaceMap);
|
||||
if (newWildcardType == null) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound());
|
||||
}
|
||||
|
||||
ArgType[] genericTypes = replaceType.getGenericTypes();
|
||||
if (replaceType.isGeneric() && genericTypes != null && genericTypes.length != 0) {
|
||||
int size = genericTypes.length;
|
||||
ArgType[] newTypes = new ArgType[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
ArgType genericType = genericTypes[i];
|
||||
ArgType type = replaceTypeVariablesUsingMap(genericType, replaceMap);
|
||||
if (type == null) {
|
||||
type = genericType;
|
||||
}
|
||||
newTypes[i] = type;
|
||||
}
|
||||
return ArgType.generic(replaceType.getObject(), newTypes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,11 @@ import jadx.core.utils.Utils;
|
||||
|
||||
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
public static final Object DEFAULT_CASE_KEY = new Object();
|
||||
|
||||
private final BlockNode header;
|
||||
|
||||
private final List<CaseInfo> cases;
|
||||
private IContainer defCase;
|
||||
|
||||
public SwitchRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
@@ -50,14 +51,6 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
cases.add(new CaseInfo(keysList, c));
|
||||
}
|
||||
|
||||
public void setDefaultCase(IContainer block) {
|
||||
defCase = block;
|
||||
}
|
||||
|
||||
public IContainer getDefaultCase() {
|
||||
return defCase;
|
||||
}
|
||||
|
||||
public List<CaseInfo> getCases() {
|
||||
return cases;
|
||||
}
|
||||
@@ -68,23 +61,15 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
List<IContainer> all = new ArrayList<>(cases.size() + 2);
|
||||
List<IContainer> all = new ArrayList<>(cases.size() + 1);
|
||||
all.add(header);
|
||||
all.addAll(getCaseContainers());
|
||||
if (defCase != null) {
|
||||
all.add(defCase);
|
||||
}
|
||||
return Collections.unmodifiableList(all);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getBranches() {
|
||||
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
|
||||
branches.addAll(getCaseContainers());
|
||||
if (defCase != null) {
|
||||
branches.add(defCase);
|
||||
}
|
||||
return Collections.unmodifiableList(branches);
|
||||
return Collections.unmodifiableList(getCaseContainers());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,13 +82,12 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Switch: ").append(cases.size());
|
||||
for (CaseInfo caseInfo : cases) {
|
||||
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
|
||||
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
|
||||
sb.append(CodeWriter.NL).append(" case ")
|
||||
.append(Utils.listToString(caseInfo.getKeys()))
|
||||
.append(Utils.listToString(keyStrings))
|
||||
.append(" -> ").append(caseInfo.getContainer());
|
||||
}
|
||||
if (defCase != null) {
|
||||
sb.append(CodeWriter.NL).append(" default -> ").append(defCase);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.utils.MethodUtils;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Attach Method Details",
|
||||
desc = "Attach method details for invoke instructions",
|
||||
runBefore = {
|
||||
CodeShrinkVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachMethodDetails extends AbstractVisitor {
|
||||
|
||||
private MethodUtils methodUtils;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
methodUtils = root.getMethodUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode blockNode : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
attachMethodDetails((BaseInvokeNode) insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attachMethodDetails(BaseInvokeNode insn) {
|
||||
IMethodDetails methodDetails = methodUtils.getMethodDetails(insn.getCallMth());
|
||||
if (methodDetails != null) {
|
||||
insn.addAttr(methodDetails);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -240,8 +240,8 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
|
||||
if (insn instanceof CallMthInterface) {
|
||||
CallMthInterface callInsn = (CallMthInterface) insn;
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
|
||||
MethodInfo callMth = callInsn.getCallMth();
|
||||
int offset = callInsn.getFirstArgOffset();
|
||||
int argIndex = insn.getArgIndex(arg);
|
||||
|
||||
@@ -54,7 +54,7 @@ public class DeboxingVisitor extends AbstractVisitor {
|
||||
private static MethodInfo valueOfMth(RootNode root, ArgType argType, String clsName) {
|
||||
ArgType boxType = ArgType.object(clsName);
|
||||
ClassInfo boxCls = ClassInfo.fromType(root, boxType);
|
||||
return MethodInfo.externalMth(boxCls, "valueOf", Collections.singletonList(argType), boxType);
|
||||
return MethodInfo.fromDetails(root, boxCls, "valueOf", Collections.singletonList(argType), boxType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
private static final String NL = "\\l";
|
||||
private static final boolean PRINT_DOMINATORS = false;
|
||||
private static final boolean PRINT_DOMINATORS_INFO = false;
|
||||
|
||||
private final boolean useRegions;
|
||||
private final boolean rawInsn;
|
||||
@@ -182,6 +183,14 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
if (!attrs.isEmpty()) {
|
||||
dot.add('|').add(attrs);
|
||||
}
|
||||
if (PRINT_DOMINATORS_INFO) {
|
||||
dot.add('|');
|
||||
dot.startLine("doms: ").add(escape(block.getDoms()));
|
||||
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
|
||||
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
|
||||
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
|
||||
dot.startLine("\\l");
|
||||
}
|
||||
String insns = insertInsns(mth, block);
|
||||
if (!insns.isEmpty()) {
|
||||
dot.add('|').add(insns);
|
||||
@@ -272,6 +281,13 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private String escape(Object obj) {
|
||||
if (obj == null) {
|
||||
return "null";
|
||||
}
|
||||
return escape(obj.toString());
|
||||
}
|
||||
|
||||
private String escape(String string) {
|
||||
return string
|
||||
.replace("\\", "") // TODO replace \"
|
||||
|
||||
@@ -37,6 +37,7 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockInsnPair;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
@@ -44,7 +45,8 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
runAfter = { CodeShrinkVisitor.class, ModVisitor.class }
|
||||
runAfter = { CodeShrinkVisitor.class, ModVisitor.class },
|
||||
runBefore = { ExtractFieldInit.class }
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@@ -72,8 +74,6 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (classInitMth.getBasicBlocks().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
BlockNode staticBlock = classInitMth.getBasicBlocks().get(0);
|
||||
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
|
||||
// search "$VALUES" field (holds all enum values)
|
||||
@@ -104,34 +104,32 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
List<InsnNode> toRemove = new ArrayList<>();
|
||||
|
||||
// search "$VALUES" array init and collect enum fields
|
||||
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
|
||||
if (valuesInitPair == null) {
|
||||
return false;
|
||||
}
|
||||
BlockNode staticBlock = valuesInitPair.getBlock();
|
||||
InsnNode valuesInitInsn = valuesInitPair.getInsn();
|
||||
|
||||
List<EnumField> enumFields = null;
|
||||
for (InsnNode insn : staticBlock.getInstructions()) {
|
||||
if (insn.getType() != InsnType.SPUT) {
|
||||
continue;
|
||||
}
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
if (f.equals(valuesField.getFieldInfo())) {
|
||||
InsnArg arrArg = insn.getArg(0);
|
||||
if (arrArg.isInsnWrap()) {
|
||||
InsnNode arrFillInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
||||
InsnType insnType = arrFillInsn.getType();
|
||||
if (insnType == InsnType.FILLED_NEW_ARRAY) {
|
||||
enumFields = extractEnumFields(cls, arrFillInsn, staticBlock, toRemove);
|
||||
} else if (insnType == InsnType.NEW_ARRAY) {
|
||||
// empty enum
|
||||
InsnArg arg = arrFillInsn.getArg(0);
|
||||
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0) {
|
||||
enumFields = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
InsnArg arrArg = valuesInitInsn.getArg(0);
|
||||
if (arrArg.isInsnWrap()) {
|
||||
InsnNode arrFillInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
||||
InsnType insnType = arrFillInsn.getType();
|
||||
if (insnType == InsnType.FILLED_NEW_ARRAY) {
|
||||
enumFields = extractEnumFields(cls, arrFillInsn, staticBlock, toRemove);
|
||||
} else if (insnType == InsnType.NEW_ARRAY) {
|
||||
// empty enum
|
||||
InsnArg arg = arrFillInsn.getArg(0);
|
||||
if (arg.isLiteral() && ((LiteralArg) arg).getLiteral() == 0) {
|
||||
enumFields = Collections.emptyList();
|
||||
}
|
||||
toRemove.add(insn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (enumFields == null) {
|
||||
return false;
|
||||
}
|
||||
toRemove.add(valuesInitInsn);
|
||||
|
||||
// all checks complete, perform transform
|
||||
EnumClassAttr attr = new EnumClassAttr(enumFields.size());
|
||||
@@ -169,6 +167,22 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
|
||||
FieldInfo searchField = valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
|
||||
FieldInfo f = (FieldInfo) indexInsnNode.getIndex();
|
||||
if (f.equals(searchField)) {
|
||||
return new BlockInsnPair(blockNode, indexInsnNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<EnumField> extractEnumFields(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
List<EnumField> enumFields = new ArrayList<>();
|
||||
for (InsnArg arg : arrFillInsn.getArguments()) {
|
||||
|
||||
@@ -35,9 +35,6 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (cls.isEnum()) {
|
||||
return false;
|
||||
}
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
@@ -66,12 +63,11 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove final field in place initialization if it assign in class init method
|
||||
* Remove a final field in place initialization if it an assign found in class init method
|
||||
*/
|
||||
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = cls.getClassInfo().getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
if (field.getDeclClass().equals(cls.getClassInfo())) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal()) {
|
||||
fn.remove(AType.FIELD_INIT);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "FindSuperUsageVisitor",
|
||||
desc = "Finds variables where a member of the super class is used and marks them.",
|
||||
runBefore = BlockSplitter.class
|
||||
)
|
||||
public class FindSuperUsageVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
process(mth);
|
||||
}
|
||||
|
||||
private static void process(MethodNode methodNode) {
|
||||
ArgType superClass = methodNode.getParentClass().getSuperClass();
|
||||
if (superClass == null) {
|
||||
return;
|
||||
}
|
||||
String superClassName = superClass.getObject();
|
||||
if (superClassName.equals("java.lang.Object")) {
|
||||
return;
|
||||
}
|
||||
for (InsnNode instruction : methodNode.getInstructions()) {
|
||||
if (instruction != null) {
|
||||
for (InsnArg argument : instruction.getArguments()) {
|
||||
if (argument.isRegister()) {
|
||||
ArgType argumentType = ((RegisterArg) argument).getInitType();
|
||||
if (argumentType.isObject() && argumentType.getObject().equals(superClassName)) {
|
||||
argument.add(AFlag.SUPER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "MethodInvokeVisitor",
|
||||
desc = "Process additional info for method invocation (overload, vararg)",
|
||||
runAfter = {
|
||||
CodeShrinkVisitor.class,
|
||||
ModVisitor.class
|
||||
},
|
||||
runBefore = {
|
||||
SimplifyVisitor.class // run before cast remove and StringBuilder replace
|
||||
}
|
||||
)
|
||||
public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
private RootNode root;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
processInsn(mth, insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processInsn(MethodNode mth, InsnNode insn) {
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
processInvoke(mth, ((BaseInvokeNode) insn));
|
||||
}
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
if (insnArg instanceof InsnWrapArg) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) insnArg).getWrapInsn();
|
||||
processInsn(mth, wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processInvoke(MethodNode parentMth, BaseInvokeNode invokeInsn) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
if (callMth.getArgsCount() == 0) {
|
||||
return;
|
||||
}
|
||||
IMethodDetails mthDetails = root.getMethodUtils().getMethodDetails(invokeInsn);
|
||||
if (mthDetails == null) {
|
||||
if (Consts.DEBUG) {
|
||||
parentMth.addComment("JADX DEBUG: Method info not found: " + callMth);
|
||||
}
|
||||
processUnknown(invokeInsn);
|
||||
} else {
|
||||
// parentMth.addComment("JADX DEBUG: got method details: " + mthDetails);
|
||||
if (mthDetails.isVarArg()) {
|
||||
ArgType last = Utils.last(mthDetails.getArgTypes());
|
||||
if (last != null && last.isArray()) {
|
||||
invokeInsn.add(AFlag.VARARG_CALL);
|
||||
}
|
||||
}
|
||||
processOverloaded(parentMth, invokeInsn, mthDetails);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOverloaded(MethodNode parentMth, BaseInvokeNode invokeInsn, IMethodDetails mthDetails) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
ArgType callCls = getCallClassFromInvoke(parentMth, invokeInsn, callMth);
|
||||
List<IMethodDetails> overloadMethods = root.getMethodUtils().collectOverloadedMethods(callCls, callMth);
|
||||
if (overloadMethods.isEmpty()) {
|
||||
// not overloaded
|
||||
return;
|
||||
}
|
||||
|
||||
overloadMethods.add(mthDetails);
|
||||
resolveTypeVariablesInMethodArgs(invokeInsn, mthDetails, overloadMethods);
|
||||
|
||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||
List<ArgType> castTypes = searchCastTypes(parentMth, mthDetails, overloadMethods, compilerVarTypes);
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method details not found => add cast for 'null' args
|
||||
*/
|
||||
private void processUnknown(BaseInvokeNode invokeInsn) {
|
||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||
List<ArgType> castTypes = new ArrayList<>(compilerVarTypes);
|
||||
if (replaceUnknownTypes(castTypes, invokeInsn.getCallMth().getArgumentsTypes())) {
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType getCallClassFromInvoke(MethodNode parentMth, BaseInvokeNode invokeInsn, MethodInfo callMth) {
|
||||
if (invokeInsn instanceof ConstructorInsn) {
|
||||
ConstructorInsn constrInsn = (ConstructorInsn) invokeInsn;
|
||||
if (constrInsn.isSuper()) {
|
||||
return parentMth.getParentClass().getSuperClass();
|
||||
}
|
||||
}
|
||||
InsnArg instanceArg = invokeInsn.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
return instanceArg.getType();
|
||||
}
|
||||
// static call
|
||||
return callMth.getDeclClass().getType();
|
||||
}
|
||||
|
||||
private void resolveTypeVariablesInMethodArgs(BaseInvokeNode invokeInsn, IMethodDetails mthDetails,
|
||||
List<IMethodDetails> overloadedMethods) {
|
||||
MethodInfo callMth = invokeInsn.getCallMth();
|
||||
ArgType declClsType = callMth.getDeclClass().getType();
|
||||
ArgType callClsType;
|
||||
InsnArg instanceArg = invokeInsn.getInstanceArg();
|
||||
if (instanceArg != null) {
|
||||
callClsType = instanceArg.getType();
|
||||
} else {
|
||||
callClsType = declClsType;
|
||||
}
|
||||
|
||||
Map<ArgType, ArgType> typeVarsMapping = root.getTypeUtils().getTypeVariablesMapping(callClsType);
|
||||
resolveTypeVars(mthDetails, typeVarsMapping);
|
||||
for (IMethodDetails m : overloadedMethods) {
|
||||
resolveTypeVars(m, typeVarsMapping);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List<ArgType> compilerVarTypes, List<ArgType> castTypes) {
|
||||
int argsCount = invokeInsn.getArgsCount();
|
||||
for (int i = argsOffset; i < argsCount; i++) {
|
||||
InsnArg arg = invokeInsn.getArg(i);
|
||||
int origPos = i - argsOffset;
|
||||
ArgType compilerType = compilerVarTypes.get(origPos);
|
||||
ArgType castType = castTypes.get(origPos);
|
||||
if (castType != null) {
|
||||
if (!castType.equals(compilerType)) {
|
||||
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
|
||||
arg.setType(castType);
|
||||
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||
} else {
|
||||
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||
castInsn.addArg(arg);
|
||||
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||
InsnArg wrapCast = InsnArg.wrapArg(castInsn);
|
||||
wrapCast.setType(castType);
|
||||
invokeInsn.setArg(i, wrapCast);
|
||||
}
|
||||
} else {
|
||||
// protect already existed cast
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (wrapInsn.getType() == InsnType.CHECK_CAST) {
|
||||
wrapInsn.add(AFlag.EXPLICIT_CAST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveTypeVars(IMethodDetails mthDetails, Map<ArgType, ArgType> typeVarsMapping) {
|
||||
List<ArgType> argTypes = mthDetails.getArgTypes();
|
||||
int argsCount = argTypes.size();
|
||||
for (int argNum = 0; argNum < argsCount; argNum++) {
|
||||
ArgType argType = argTypes.get(argNum);
|
||||
if (argType == null) {
|
||||
throw new JadxRuntimeException("Null arg type in " + mthDetails + " at: " + argNum + " in: " + argTypes);
|
||||
}
|
||||
if (argType.containsTypeVariable()) {
|
||||
ArgType resolvedType = root.getTypeUtils().replaceTypeVariablesUsingMap(argType, typeVarsMapping);
|
||||
if (resolvedType == null || resolvedType.containsTypeVariable()) {
|
||||
// type variables erased from method info by compiler
|
||||
resolvedType = mthDetails.getMethodInfo().getArgumentsTypes().get(argNum);
|
||||
}
|
||||
argTypes.set(argNum, resolvedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
|
||||
List<ArgType> compilerVarTypes) {
|
||||
// try compile types
|
||||
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
|
||||
return compilerVarTypes;
|
||||
}
|
||||
int argsCount = compilerVarTypes.size();
|
||||
List<ArgType> castTypes = new ArrayList<>(compilerVarTypes);
|
||||
|
||||
// replace unknown types
|
||||
boolean changed = replaceUnknownTypes(castTypes, mthDetails.getArgTypes());
|
||||
if (changed && isOverloadResolved(mthDetails, overloadedMethods, castTypes)) {
|
||||
return castTypes;
|
||||
}
|
||||
|
||||
// replace generic types
|
||||
changed = false;
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType castType = castTypes.get(i);
|
||||
ArgType mthType = mthDetails.getArgTypes().get(i);
|
||||
if (!castType.isGeneric() && mthType.isGeneric()) {
|
||||
castTypes.set(i, mthType);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed && isOverloadResolved(mthDetails, overloadedMethods, castTypes)) {
|
||||
return castTypes;
|
||||
}
|
||||
|
||||
// if just one arg => cast will resolve
|
||||
if (argsCount == 1) {
|
||||
return mthDetails.getArgTypes();
|
||||
}
|
||||
// TODO: try to minimize casts count
|
||||
parentMth.addComment("JADX DEBUG: Failed to find minimal casts for resolve overloaded methods, cast all args instead"
|
||||
+ NL + " method: " + mthDetails
|
||||
+ NL + " arg types: " + compilerVarTypes
|
||||
+ NL + " candidates:"
|
||||
+ NL + " " + Utils.listToString(overloadedMethods, NL + " "));
|
||||
|
||||
// not resolved -> cast all args
|
||||
return mthDetails.getArgTypes();
|
||||
}
|
||||
|
||||
private boolean replaceUnknownTypes(List<ArgType> castTypes, List<ArgType> mthArgTypes) {
|
||||
int argsCount = castTypes.size();
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType castType = castTypes.get(i);
|
||||
if (!castType.isTypeKnown()) {
|
||||
ArgType mthType = mthArgTypes.get(i);
|
||||
castTypes.set(i, mthType);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
|
||||
if (overloadedMethods.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// TODO: search closest method, instead filtering
|
||||
List<IMethodDetails> strictMethods = filterApplicableMethods(overloadedMethods, castTypes, MethodInvokeVisitor::isStrictTypes);
|
||||
if (strictMethods.size() == 1) {
|
||||
return strictMethods.get(0).equals(expectedMthDetails);
|
||||
}
|
||||
List<IMethodDetails> resolvedMethods = filterApplicableMethods(overloadedMethods, castTypes, MethodInvokeVisitor::isTypeApplicable);
|
||||
if (resolvedMethods.size() == 1) {
|
||||
return resolvedMethods.get(0).equals(expectedMthDetails);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isStrictTypes(TypeCompareEnum result) {
|
||||
return result.isEqual();
|
||||
}
|
||||
|
||||
private static boolean isTypeApplicable(TypeCompareEnum result) {
|
||||
return result.isNarrowOrEqual() || result == TypeCompareEnum.WIDER_BY_GENERIC;
|
||||
}
|
||||
|
||||
private List<IMethodDetails> filterApplicableMethods(List<IMethodDetails> methods, List<ArgType> types,
|
||||
Function<TypeCompareEnum, Boolean> acceptFunction) {
|
||||
List<IMethodDetails> list = new ArrayList<>(methods.size());
|
||||
for (IMethodDetails m : methods) {
|
||||
if (isMethodAcceptable(m, types, acceptFunction)) {
|
||||
list.add(m);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean isMethodAcceptable(IMethodDetails methodDetails, List<ArgType> types,
|
||||
Function<TypeCompareEnum, Boolean> acceptFunction) {
|
||||
List<ArgType> mthTypes = methodDetails.getArgTypes();
|
||||
int argCount = mthTypes.size();
|
||||
if (argCount != types.size()) {
|
||||
return false;
|
||||
}
|
||||
TypeCompare typeCompare = root.getTypeUpdate().getTypeCompare();
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
ArgType mthType = mthTypes.get(i);
|
||||
ArgType argType = types.get(i);
|
||||
TypeCompareEnum result = typeCompare.compareTypes(argType, mthType);
|
||||
if (!acceptFunction.apply(result)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<ArgType> collectCompilerVarTypes(BaseInvokeNode insn, int argOffset) {
|
||||
int argsCount = insn.getArgsCount();
|
||||
List<ArgType> result = new ArrayList<>(argsCount);
|
||||
for (int i = argOffset; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
result.add(getCompilerVarType(arg));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type as seen by compiler
|
||||
*/
|
||||
private ArgType getCompilerVarType(InsnArg arg) {
|
||||
if (arg instanceof LiteralArg) {
|
||||
LiteralArg literalArg = (LiteralArg) arg;
|
||||
ArgType type = literalArg.getType();
|
||||
if (literalArg.getLiteral() == 0) {
|
||||
if (type.isObject() || type.isArray()) {
|
||||
// null
|
||||
return ArgType.UNKNOWN_OBJECT;
|
||||
}
|
||||
}
|
||||
if (type.isPrimitive() && !arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE)) {
|
||||
return ArgType.INT;
|
||||
}
|
||||
return arg.getType();
|
||||
}
|
||||
if (arg instanceof RegisterArg) {
|
||||
return arg.getType();
|
||||
}
|
||||
if (arg instanceof InsnWrapArg) {
|
||||
InsnWrapArg wrapArg = (InsnWrapArg) arg;
|
||||
InsnNode wrapInsn = wrapArg.getWrapInsn();
|
||||
if (wrapInsn.getResult() != null) {
|
||||
return wrapInsn.getResult().getType();
|
||||
}
|
||||
return arg.getType();
|
||||
}
|
||||
throw new JadxRuntimeException("Unknown var type for: " + arg);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
InsnRemover remover = new InsnRemover(mth);
|
||||
replaceStep(mth, remover);
|
||||
removeStep(mth, remover);
|
||||
iterativeRemoveStep(mth);
|
||||
}
|
||||
|
||||
private static void replaceStep(MethodNode mth, InsnRemover remover) {
|
||||
@@ -199,15 +200,27 @@ public class ModVisitor extends AbstractVisitor {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : annotation.getValues().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
FieldNode constField = parentCls.getConstField(value);
|
||||
if (constField != null) {
|
||||
entry.setValue(constField.getFieldInfo());
|
||||
}
|
||||
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private Object replaceConstValue(ClassNode parentCls, @Nullable Object value) {
|
||||
if (value instanceof List) {
|
||||
List listVal = (List) value;
|
||||
if (!listVal.isEmpty()) {
|
||||
listVal.replaceAll(v -> replaceConstValue(parentCls, v));
|
||||
}
|
||||
return listVal;
|
||||
}
|
||||
FieldNode constField = parentCls.getConstField(value);
|
||||
if (constField != null) {
|
||||
return constField.getFieldInfo();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
|
||||
FieldNode f;
|
||||
if (insn.getType() == InsnType.CONST_STR) {
|
||||
@@ -254,7 +267,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
private static void removeRedundantCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType castType = (ArgType) insn.getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), castArg.getType(), castType)
|
||||
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|
||||
|| isCastDuplicate(insn)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setResult(insn.getResult());
|
||||
@@ -293,6 +306,9 @@ public class ModVisitor extends AbstractVisitor {
|
||||
break;
|
||||
|
||||
default:
|
||||
if (insn.contains(AFlag.REMOVE)) {
|
||||
remover.addAndUnbind(insn);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -300,6 +316,33 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void iterativeRemoveStep(MethodNode mth) {
|
||||
boolean changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.MOVE
|
||||
&& insn.isAttrStorageEmpty()
|
||||
&& isResultArgNotUsed(insn)) {
|
||||
InsnRemover.remove(mth, block, insn);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
}
|
||||
|
||||
private static boolean isResultArgNotUsed(InsnNode insn) {
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null) {
|
||||
SSAVar ssaVar = result.getSVar();
|
||||
return ssaVar.getUseCount() == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodInfo callMth = co.getCallMth();
|
||||
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
||||
|
||||
@@ -49,7 +49,8 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
stringGetBytesMth = MethodInfo.externalMth(
|
||||
stringGetBytesMth = MethodInfo.fromDetails(
|
||||
root,
|
||||
ClassInfo.fromType(root, ArgType.STRING),
|
||||
"getBytes",
|
||||
Collections.emptyList(),
|
||||
@@ -224,7 +225,7 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
ArgType castToType = (ArgType) castInsn.getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), argType, castToType)
|
||||
if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|
||||
|| isCastDuplicate(castInsn)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setOffset(castInsn.getOffset());
|
||||
|
||||
@@ -16,7 +16,6 @@ import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -422,7 +421,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean mergeConstReturn(MethodNode mth) {
|
||||
if (mth.getReturnType() == ArgType.VOID) {
|
||||
if (mth.isVoidReturn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+3
-11
@@ -64,7 +64,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
mth.getSVars().forEach(var -> {
|
||||
ArgType type = var.getTypeInfo().getType();
|
||||
if (!type.isTypeKnown()) {
|
||||
mth.addComment("JADX WARNING: type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||
mth.addWarnComment("Type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
} else {
|
||||
LOG.warn("Multiple debug info for {}: {}", ssaVar, debugInfoSet);
|
||||
mth.addComment("JADX INFO: Multiple debug info for " + ssaVar + ": " + debugInfoSet);
|
||||
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
}
|
||||
@@ -150,14 +150,6 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
if (NameMapper.isValidAndPrintable(varName)) {
|
||||
ssaVar.setName(varName);
|
||||
}
|
||||
detachDebugInfo(ssaVar.getAssign());
|
||||
ssaVar.getUseList().forEach(DebugInfoApplyVisitor::detachDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void detachDebugInfo(RegisterArg reg) {
|
||||
if (reg != null) {
|
||||
reg.remove(AType.REG_DEBUG_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +164,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
* Fix debug info for splitter 'return' instructions
|
||||
*/
|
||||
private static void fixLinesForReturn(MethodNode mth) {
|
||||
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||
if (mth.isVoidReturn()) {
|
||||
return;
|
||||
}
|
||||
InsnNode origReturn = null;
|
||||
|
||||
@@ -20,6 +20,8 @@ import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Debug Info Parser",
|
||||
desc = "Parse debug information (variable names and types, instruction lines)",
|
||||
@@ -42,7 +44,7 @@ public class DebugInfoParseVisitor extends AbstractVisitor {
|
||||
} catch (Exception e) {
|
||||
mth.addComment("JADX WARNING: Error to parse debug info: "
|
||||
+ ErrorsCounter.formatMsg(mth, e.getMessage())
|
||||
+ '\n' + Utils.getStackTrace(e));
|
||||
+ NL + Utils.getStackTrace(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public final class LocalVar {
|
||||
@@ -31,7 +32,7 @@ public final class LocalVar {
|
||||
this.name = name;
|
||||
if (sign != null) {
|
||||
try {
|
||||
ArgType gType = ArgType.parseGenericSignature(sign);
|
||||
ArgType gType = new SignatureParser(sign).consumeType();
|
||||
if (checkSignature(type, gType)) {
|
||||
type = gType;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors.regions;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -96,7 +95,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||
if (!mth.isVoidReturn()
|
||||
&& hasSimpleReturnBlock(ifRegion.getElseRegion())
|
||||
/* && insnsCount(ifRegion.getThenRegion()) < 2 */) {
|
||||
invertIfRegion(ifRegion);
|
||||
@@ -139,7 +138,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
// code style check:
|
||||
// will remove 'return;' from 'then' and 'else' with one instruction
|
||||
// see #jadx.tests.integration.conditions.TestConditions9
|
||||
if (mth.getReturnType() == ArgType.VOID
|
||||
if (mth.isVoidReturn()
|
||||
&& insnsCount(ifRegion.getThenRegion()) == 2
|
||||
&& insnsCount(ifRegion.getElseRegion()) == 2) {
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.regions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -10,6 +11,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -737,135 +739,71 @@ public class RegionMaker {
|
||||
}
|
||||
|
||||
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) {
|
||||
SwitchRegion sw = new SwitchRegion(currentRegion, block);
|
||||
currentRegion.getSubBlocks().add(sw);
|
||||
|
||||
// map case blocks to keys
|
||||
int len = insn.getTargets().length;
|
||||
// sort by target
|
||||
Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<>(len);
|
||||
Object[] keysArr = insn.getKeys();
|
||||
BlockNode[] targetBlocksArr = insn.getTargetBlocks();
|
||||
for (int i = 0; i < len; i++) {
|
||||
Object key = insn.getKeys()[i];
|
||||
BlockNode targ = insn.getTargetBlocks()[i];
|
||||
List<Object> keys = blocksMap.computeIfAbsent(targ, k -> new ArrayList<>(2));
|
||||
keys.add(key);
|
||||
List<Object> keys = blocksMap.computeIfAbsent(targetBlocksArr[i], k -> new ArrayList<>(2));
|
||||
keys.add(keysArr[i]);
|
||||
}
|
||||
BlockNode defCase = insn.getDefTargetBlock();
|
||||
if (defCase != null) {
|
||||
blocksMap.remove(defCase);
|
||||
List<Object> keys = blocksMap.computeIfAbsent(defCase, k -> new ArrayList<>(1));
|
||||
keys.add(SwitchRegion.DEFAULT_CASE_KEY);
|
||||
}
|
||||
|
||||
// search 'out' block - 'next' block after whole switch statement
|
||||
BlockNode out;
|
||||
LoopInfo loop = mth.getLoopForBlock(block);
|
||||
|
||||
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
||||
|
||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
||||
BitSet outs = new BitSet(basicBlocks.size());
|
||||
outs.or(block.getDomFrontier());
|
||||
for (BlockNode s : block.getCleanSuccessors()) {
|
||||
BitSet df = s.getDomFrontier();
|
||||
// fall through case block
|
||||
if (df.cardinality() > 1) {
|
||||
if (df.cardinality() > 2) {
|
||||
LOG.debug("Unexpected case pattern, block: {}, mth: {}", s, mth);
|
||||
} else {
|
||||
BlockNode first = basicBlocks.get(df.nextSetBit(0));
|
||||
BlockNode second = basicBlocks.get(df.nextSetBit(first.getId() + 1));
|
||||
if (second.getDomFrontier().get(first.getId())) {
|
||||
fallThroughCases.put(s, second);
|
||||
df = new BitSet(df.size());
|
||||
df.set(first.getId());
|
||||
} else if (first.getDomFrontier().get(second.getId())) {
|
||||
fallThroughCases.put(s, first);
|
||||
df = new BitSet(df.size());
|
||||
df.set(second.getId());
|
||||
}
|
||||
}
|
||||
if (loop == null) {
|
||||
out = calcPostDomOut(mth, block, mth.getExitBlocks());
|
||||
} else {
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
// treat 'continue' as exit
|
||||
out = calcPostDomOut(mth, block, loopEnd.getPredecessors());
|
||||
if (out != null) {
|
||||
insertContinueInSwitch(block, out, loopEnd);
|
||||
} else {
|
||||
// no 'continue'
|
||||
out = calcPostDomOut(mth, block, Collections.singletonList(loopEnd));
|
||||
}
|
||||
outs.or(df);
|
||||
}
|
||||
outs.clear(block.getId());
|
||||
if (loop != null) {
|
||||
outs.clear(loop.getStart().getId());
|
||||
}
|
||||
|
||||
SwitchRegion sw = new SwitchRegion(currentRegion, block);
|
||||
currentRegion.getSubBlocks().add(sw);
|
||||
stack.push(sw);
|
||||
stack.addExits(BlockUtils.bitSetToBlocks(mth, outs));
|
||||
stack.addExit(out);
|
||||
|
||||
// check cases order if fall through case exists
|
||||
if (!fallThroughCases.isEmpty()
|
||||
&& isBadCasesOrder(blocksMap, fallThroughCases)) {
|
||||
LOG.debug("Fixing incorrect switch cases order, method: {}", mth);
|
||||
blocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
|
||||
if (isBadCasesOrder(blocksMap, fallThroughCases)) {
|
||||
mth.addWarn("Can't fix incorrect switch cases order");
|
||||
// detect fallthrough cases
|
||||
Map<BlockNode, BlockNode> fallThroughCases = new LinkedHashMap<>();
|
||||
if (out != null) {
|
||||
BitSet caseBlocks = BlockUtils.blocksToBitSet(mth, blocksMap.keySet());
|
||||
caseBlocks.clear(out.getId());
|
||||
for (BlockNode successor : block.getCleanSuccessors()) {
|
||||
BlockNode fallThroughBlock = searchFallThroughCase(successor, out, caseBlocks);
|
||||
if (fallThroughBlock != null) {
|
||||
fallThroughCases.put(successor, fallThroughBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter 'out' block
|
||||
if (outs.cardinality() > 1) {
|
||||
// remove exception handlers
|
||||
BlockUtils.cleanBitSet(mth, outs);
|
||||
}
|
||||
if (outs.cardinality() > 1) {
|
||||
// filter loop start and successors of other blocks
|
||||
for (int i = outs.nextSetBit(0); i >= 0; i = outs.nextSetBit(i + 1)) {
|
||||
BlockNode b = basicBlocks.get(i);
|
||||
outs.andNot(b.getDomFrontier());
|
||||
if (b.contains(AFlag.LOOP_START)) {
|
||||
outs.clear(b.getId());
|
||||
// check fallthrough cases order
|
||||
if (!fallThroughCases.isEmpty() && isBadCasesOrder(blocksMap, fallThroughCases)) {
|
||||
Map<BlockNode, List<Object>> newBlocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
|
||||
if (isBadCasesOrder(newBlocksMap, fallThroughCases)) {
|
||||
mth.addComment("JADX INFO: Can't fix incorrect switch cases order, some code will duplicate");
|
||||
fallThroughCases.clear();
|
||||
} else {
|
||||
for (BlockNode s : b.getCleanSuccessors()) {
|
||||
outs.clear(s.getId());
|
||||
}
|
||||
blocksMap = newBlocksMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loop != null && outs.cardinality() > 1) {
|
||||
outs.clear(loop.getEnd().getId());
|
||||
}
|
||||
if (outs.cardinality() == 0) {
|
||||
// one or several case blocks are empty,
|
||||
// run expensive algorithm for find 'out' block
|
||||
for (BlockNode maybeOut : block.getSuccessors()) {
|
||||
boolean allReached = true;
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
if (!isPathExists(s, maybeOut)) {
|
||||
allReached = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allReached) {
|
||||
outs.set(maybeOut.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
BlockNode out = null;
|
||||
if (outs.cardinality() == 1) {
|
||||
out = basicBlocks.get(outs.nextSetBit(0));
|
||||
stack.addExit(out);
|
||||
} else if (loop == null && outs.cardinality() > 1) {
|
||||
LOG.warn("Can't detect out node for switch block: {} in {}", block, mth);
|
||||
}
|
||||
if (loop != null) {
|
||||
// check if 'continue' must be inserted
|
||||
BlockNode end = loop.getEnd();
|
||||
if (out != end && out != null) {
|
||||
insertContinueInSwitch(block, out, end);
|
||||
}
|
||||
}
|
||||
|
||||
if (!stack.containsExit(defCase)) {
|
||||
Region defRegion = makeRegion(defCase, stack);
|
||||
if (RegionUtils.notEmpty(defRegion)) {
|
||||
sw.setDefaultCase(defRegion);
|
||||
}
|
||||
}
|
||||
for (Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
||||
List<Object> keysList = entry.getValue();
|
||||
BlockNode caseBlock = entry.getKey();
|
||||
if (stack.containsExit(caseBlock)) {
|
||||
// empty case block
|
||||
sw.addCase(entry.getValue(), new Region(stack.peekRegion()));
|
||||
sw.addCase(keysList, new Region(stack.peekRegion()));
|
||||
} else {
|
||||
BlockNode next = fallThroughCases.get(caseBlock);
|
||||
stack.addExit(next);
|
||||
@@ -875,17 +813,102 @@ public class RegionMaker {
|
||||
next.add(AFlag.FALL_THROUGH);
|
||||
caseRegion.add(AFlag.FALL_THROUGH);
|
||||
}
|
||||
sw.addCase(entry.getValue(), caseRegion);
|
||||
sw.addCase(keysList, caseRegion);
|
||||
// 'break' instruction will be inserted in RegionMakerVisitor.PostRegionVisitor
|
||||
}
|
||||
}
|
||||
|
||||
removeEmptyCases(insn, sw, defCase);
|
||||
|
||||
stack.pop();
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean isBadCasesOrder(Map<BlockNode, List<Object>> blocksMap,
|
||||
Map<BlockNode, BlockNode> fallThroughCases) {
|
||||
@Nullable
|
||||
private BlockNode searchFallThroughCase(BlockNode successor, BlockNode out, BitSet caseBlocks) {
|
||||
BitSet df = successor.getDomFrontier();
|
||||
if (df.intersects(caseBlocks)) {
|
||||
return getOneIntersectionBlock(out, caseBlocks, df);
|
||||
}
|
||||
Set<BlockNode> allPathsBlocks = BlockUtils.getAllPathsBlocks(successor, out);
|
||||
Map<BlockNode, BitSet> bitSetMap = BlockUtils.calcPartialPostDominance(mth, allPathsBlocks, out);
|
||||
BitSet pdoms = bitSetMap.get(successor);
|
||||
if (pdoms != null && pdoms.intersects(caseBlocks)) {
|
||||
return getOneIntersectionBlock(out, caseBlocks, pdoms);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BlockNode getOneIntersectionBlock(BlockNode out, BitSet caseBlocks, BitSet fallThroughSet) {
|
||||
BitSet caseExits = BlockUtils.copyBlocksBitSet(mth, fallThroughSet);
|
||||
caseExits.clear(out.getId());
|
||||
caseExits.and(caseBlocks);
|
||||
return BlockUtils.bitSetToOneBlock(mth, caseExits);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BlockNode calcPostDomOut(MethodNode mth, BlockNode block, List<BlockNode> exits) {
|
||||
if (exits.size() == 1 && mth.getExitBlocks().equals(exits)) {
|
||||
// simple case: for only one exit which is equal to method exit block
|
||||
return BlockUtils.calcImmediatePostDominator(mth, block);
|
||||
}
|
||||
// fast search: union of blocks dominance frontier
|
||||
// work if no fallthrough cases and no returns inside switch
|
||||
BitSet outs = BlockUtils.copyBlocksBitSet(mth, block.getDomFrontier());
|
||||
for (BlockNode s : block.getCleanSuccessors()) {
|
||||
outs.or(s.getDomFrontier());
|
||||
}
|
||||
outs.clear(block.getId());
|
||||
|
||||
if (outs.cardinality() != 1) {
|
||||
// slow search: calculate partial post-dominance for every exit node
|
||||
BitSet ipdoms = BlockUtils.newBlocksBitSet(mth);
|
||||
for (BlockNode exitBlock : exits) {
|
||||
if (BlockUtils.isAnyPathExists(block, exitBlock)) {
|
||||
Set<BlockNode> pathBlocks = BlockUtils.getAllPathsBlocks(block, exitBlock);
|
||||
BlockNode ipdom = BlockUtils.calcPartialImmediatePostDominator(mth, block, pathBlocks, exitBlock);
|
||||
if (ipdom != null) {
|
||||
ipdoms.set(ipdom.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
outs.and(ipdoms);
|
||||
}
|
||||
return BlockUtils.bitSetToOneBlock(mth, outs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty case blocks:
|
||||
* 1. single 'default' case
|
||||
* 2. filler cases if switch is 'packed' and 'default' case is empty
|
||||
*/
|
||||
private void removeEmptyCases(SwitchNode insn, SwitchRegion sw, BlockNode defCase) {
|
||||
boolean defaultCaseIsEmpty;
|
||||
if (defCase == null) {
|
||||
defaultCaseIsEmpty = true;
|
||||
} else {
|
||||
defaultCaseIsEmpty = sw.getCases().stream()
|
||||
.anyMatch(c -> c.getKeys().contains(SwitchRegion.DEFAULT_CASE_KEY)
|
||||
&& RegionUtils.isEmpty(c.getContainer()));
|
||||
}
|
||||
if (defaultCaseIsEmpty) {
|
||||
sw.getCases().removeIf(caseInfo -> {
|
||||
if (RegionUtils.isEmpty(caseInfo.getContainer())) {
|
||||
List<Object> keys = caseInfo.getKeys();
|
||||
if (keys.contains(SwitchRegion.DEFAULT_CASE_KEY)) {
|
||||
return true;
|
||||
}
|
||||
if (insn.isPacked()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBadCasesOrder(Map<BlockNode, List<Object>> blocksMap, Map<BlockNode, BlockNode> fallThroughCases) {
|
||||
BlockNode nextCaseBlock = null;
|
||||
for (BlockNode caseBlock : blocksMap.keySet()) {
|
||||
if (nextCaseBlock != null && !caseBlock.equals(nextCaseBlock)) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
@@ -58,6 +59,8 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void postProcessRegions(MethodNode mth) {
|
||||
processForceInlineInsns(mth);
|
||||
|
||||
// make try-catch regions
|
||||
ProcessTryCatchRegions.process(mth);
|
||||
|
||||
@@ -70,6 +73,15 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processForceInlineInsns(MethodNode mth) {
|
||||
boolean needShrink = mth.getBasicBlocks().stream()
|
||||
.flatMap(block -> block.getInstructions().stream())
|
||||
.anyMatch(insn -> insn.contains(AFlag.FORCE_ASSIGN_INLINE));
|
||||
if (needShrink) {
|
||||
CodeShrinkVisitor.shrinkMethod(mth);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PostRegionVisitor extends AbstractRegionVisitor {
|
||||
@Override
|
||||
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
@@ -26,7 +25,7 @@ public class ReturnVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
// remove useless returns in void methods
|
||||
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||
if (mth.isVoidReturn()) {
|
||||
DepthRegionTraversal.traverse(mth, new ReturnRemoverVisitor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.Map;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -123,7 +122,7 @@ public class TernaryMod implements IRegionIterativeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||
if (!mth.isVoidReturn()
|
||||
&& thenInsn.getType() == InsnType.RETURN
|
||||
&& elseInsn.getType() == InsnType.RETURN) {
|
||||
InsnArg thenArg = thenInsn.getArg(0);
|
||||
|
||||
@@ -100,10 +100,6 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
|
||||
int assignPos = insnList.getIndex(assignInsn);
|
||||
if (assignPos != -1) {
|
||||
if (assignInline) {
|
||||
// TODO?
|
||||
return;
|
||||
}
|
||||
WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg);
|
||||
if (wrapInfo != null) {
|
||||
wrapList.add(wrapInfo);
|
||||
@@ -123,25 +119,19 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
|
||||
private static boolean assignInline(MethodNode mth, RegisterArg arg, InsnNode assignInsn, BlockNode assignBlock) {
|
||||
RegisterArg useArg = arg.getSVar().getUseList().get(0);
|
||||
InsnNode useInsn = useArg.getParentInsn();
|
||||
if (useInsn == null || useInsn.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsnArg replaceArg;
|
||||
InsnType assignInsnType = assignInsn.getType();
|
||||
if (assignInsnType == InsnType.MOVE || assignInsnType == InsnType.CONST) {
|
||||
replaceArg = assignInsn.getArg(0).duplicate();
|
||||
} else {
|
||||
replaceArg = InsnArg.wrapArg(assignInsn.copy());
|
||||
return false;
|
||||
}
|
||||
InsnArg replaceArg = InsnArg.wrapInsnIntoArg(assignInsn.copy());
|
||||
useInsn.replaceArg(useArg, replaceArg);
|
||||
|
||||
assignInsn.add(AFlag.REMOVE);
|
||||
assignInsn.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.remove(mth, assignBlock, assignInsn);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean inline(MethodNode mth, RegisterArg arg, InsnNode insn, BlockNode block) {
|
||||
@@ -149,6 +139,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
if (parentInsn != null && parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
}
|
||||
if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
return assignInline(mth, arg, insn, block);
|
||||
}
|
||||
boolean replaced = arg.wrapInstruction(mth, insn) != null;
|
||||
if (replaced) {
|
||||
InsnList.remove(block, insn);
|
||||
|
||||
@@ -422,6 +422,7 @@ public class SSATransform extends AbstractVisitor {
|
||||
if (resArg.getRegNum() != arg.getRegNum()
|
||||
&& !resArg.getSVar().isUsedInPhi()) {
|
||||
markThisArgs(resArg);
|
||||
parentInsn.add(AFlag.REMOVE);
|
||||
parentInsn.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
|
||||
/**
|
||||
* Special dynamic bound for invoke with generics.
|
||||
@@ -38,7 +37,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
|
||||
}
|
||||
|
||||
private ArgType getReturnType(ArgType instanceType) {
|
||||
ArgType resultGeneric = TypeUtils.replaceClassGenerics(root, instanceType, genericReturnType);
|
||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, genericReturnType);
|
||||
if (resultGeneric != null) {
|
||||
return resultGeneric;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.UNKNOWN;
|
||||
@@ -143,18 +145,56 @@ public class TypeCompare {
|
||||
if (firstGenericType && secondGenericType && !objectsEquals) {
|
||||
return CONFLICT;
|
||||
}
|
||||
boolean firstGeneric = first.isGeneric();
|
||||
boolean secondGeneric = second.isGeneric();
|
||||
|
||||
if (firstGenericType || secondGenericType) {
|
||||
ArgType firstWildcardType = first.getWildcardType();
|
||||
ArgType secondWildcardType = second.getWildcardType();
|
||||
if (firstWildcardType != null || secondWildcardType != null) {
|
||||
if (firstWildcardType != null && secondGenericType && first.getWildcardBound() == WildcardBound.UNBOUND) {
|
||||
return CONFLICT;
|
||||
}
|
||||
if (firstGenericType && secondWildcardType != null && second.getWildcardBound() == WildcardBound.UNBOUND) {
|
||||
return CONFLICT;
|
||||
}
|
||||
}
|
||||
if (firstGenericType) {
|
||||
return compareGenericTypeWithObject(first, second);
|
||||
} else {
|
||||
return compareGenericTypeWithObject(second, first).invert();
|
||||
}
|
||||
}
|
||||
boolean firstGeneric = first.isGeneric();
|
||||
boolean secondGeneric = second.isGeneric();
|
||||
if (firstGeneric != secondGeneric && objectsEquals) {
|
||||
// don't check generics for now
|
||||
return firstGeneric ? NARROW_BY_GENERIC : WIDER_BY_GENERIC;
|
||||
if (objectsEquals) {
|
||||
if (firstGeneric != secondGeneric) {
|
||||
return firstGeneric ? NARROW_BY_GENERIC : WIDER_BY_GENERIC;
|
||||
}
|
||||
// both generics on same object
|
||||
if (first.getWildcardBound() != null && second.getWildcardBound() != null) {
|
||||
// both wildcards
|
||||
return compareWildcardTypes(first, second);
|
||||
}
|
||||
ArgType[] firstGenericTypes = first.getGenericTypes();
|
||||
ArgType[] secondGenericTypes = second.getGenericTypes();
|
||||
if (firstGenericTypes == null || secondGenericTypes == null) {
|
||||
// check outer types
|
||||
ArgType firstOuterType = first.getOuterType();
|
||||
ArgType secondOuterType = second.getOuterType();
|
||||
if (firstOuterType != null && secondOuterType != null) {
|
||||
return compareTypes(firstOuterType, secondOuterType);
|
||||
}
|
||||
} else {
|
||||
// compare generics arrays
|
||||
int len = firstGenericTypes.length;
|
||||
if (len == secondGenericTypes.length) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
TypeCompareEnum res = compareTypes(firstGenericTypes[i], secondGenericTypes[i]);
|
||||
if (res != EQUAL) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean firstIsObjCls = first.equals(ArgType.OBJECT);
|
||||
if (firstIsObjCls || second.equals(ArgType.OBJECT)) {
|
||||
@@ -172,6 +212,22 @@ public class TypeCompare {
|
||||
return TypeCompareEnum.CONFLICT;
|
||||
}
|
||||
|
||||
private TypeCompareEnum compareWildcardTypes(ArgType first, ArgType second) {
|
||||
WildcardBound firstWildcardBound = first.getWildcardBound();
|
||||
WildcardBound secondWildcardBound = second.getWildcardBound();
|
||||
if (firstWildcardBound == WildcardBound.UNBOUND) {
|
||||
return WIDER;
|
||||
}
|
||||
if (secondWildcardBound == WildcardBound.UNBOUND) {
|
||||
return NARROW;
|
||||
}
|
||||
TypeCompareEnum wildcardCompare = compareTypes(first.getWildcardType(), second.getWildcardType());
|
||||
if (firstWildcardBound == secondWildcardBound) {
|
||||
return wildcardCompare;
|
||||
}
|
||||
return CONFLICT;
|
||||
}
|
||||
|
||||
private TypeCompareEnum compareGenericTypeWithObject(ArgType genericType, ArgType objType) {
|
||||
List<ArgType> extendTypes = genericType.getExtendTypes();
|
||||
if (extendTypes == null || extendTypes.isEmpty()) {
|
||||
|
||||
@@ -42,4 +42,12 @@ public enum TypeCompareEnum {
|
||||
public boolean isNarrow() {
|
||||
return this == NARROW || this == NARROW_BY_GENERIC;
|
||||
}
|
||||
|
||||
public boolean isNarrowOrEqual() {
|
||||
return isEqual() || isNarrow();
|
||||
}
|
||||
|
||||
public boolean isGeneric() {
|
||||
return this == WIDER_BY_GENERIC || this == NARROW_BY_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
+65
-32
@@ -16,7 +16,7 @@ import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
@@ -30,11 +30,13 @@ import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
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.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
@@ -48,7 +50,8 @@ import jadx.core.utils.Utils;
|
||||
desc = "Calculate best types for SSA variables",
|
||||
runAfter = {
|
||||
SSATransform.class,
|
||||
ConstInlineVisitor.class
|
||||
ConstInlineVisitor.class,
|
||||
AttachMethodDetails.class
|
||||
}
|
||||
)
|
||||
public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
@@ -68,11 +71,16 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("Start type inference in method: {}", mth);
|
||||
}
|
||||
boolean resolved = runTypePropagation(mth);
|
||||
if (!resolved) {
|
||||
boolean moveAdded = false;
|
||||
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
|
||||
moveAdded |= tryInsertAdditionalInsn(mth, var);
|
||||
if (tryInsertAdditionalInsn(mth, var)) {
|
||||
moveAdded = true;
|
||||
}
|
||||
}
|
||||
if (moveAdded) {
|
||||
InitCodeVariables.rerun(mth);
|
||||
@@ -269,11 +277,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private ITypeBound makeAssignInvokeBound(InvokeNode invokeNode) {
|
||||
MethodInfo callMth = invokeNode.getCallMth();
|
||||
ArgType boundType = callMth.getReturnType();
|
||||
ArgType genericReturnType = root.getMethodGenericReturnType(callMth);
|
||||
ArgType boundType = invokeNode.getCallMth().getReturnType();
|
||||
ArgType genericReturnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode);
|
||||
if (genericReturnType != null) {
|
||||
if (genericReturnType.containsGenericType()) {
|
||||
if (genericReturnType.containsTypeVariable()) {
|
||||
InvokeType invokeType = invokeNode.getInvokeType();
|
||||
if (invokeNode.getArgsCount() != 0
|
||||
&& invokeType != InvokeType.STATIC && invokeType != InvokeType.SUPER) {
|
||||
@@ -292,6 +299,15 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (insn == null) {
|
||||
return null;
|
||||
}
|
||||
if (insn instanceof BaseInvokeNode) {
|
||||
IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails((BaseInvokeNode) insn);
|
||||
if (methodDetails != null) {
|
||||
if (methodDetails.getArgTypes().stream().anyMatch(ArgType::containsTypeVariable)) {
|
||||
// don't add const bound for generic type variables
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg);
|
||||
}
|
||||
|
||||
@@ -361,50 +377,67 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
for (PhiInsn phiInsn : usedInPhiList) {
|
||||
if (!insertMoveForPhi(mth, phiInsn, var)) {
|
||||
if (!insertMoveForPhi(mth, phiInsn, var, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// all check passed => apply
|
||||
for (PhiInsn phiInsn : usedInPhiList) {
|
||||
insertMoveForPhi(mth, phiInsn, var, true);
|
||||
}
|
||||
mth.addComment("JADX INFO: additional move instructions added (" + usedInPhiList.size() + ") to help type inference");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean insertMoveForPhi(MethodNode mth, PhiInsn phiInsn, SSAVar var) {
|
||||
private boolean insertMoveForPhi(MethodNode mth, PhiInsn phiInsn, SSAVar var, boolean apply) {
|
||||
int argsCount = phiInsn.getArgsCount();
|
||||
for (int argIndex = 0; argIndex < argsCount; argIndex++) {
|
||||
RegisterArg reg = phiInsn.getArg(argIndex);
|
||||
if (reg.getSVar() == var) {
|
||||
BlockNode blockNode = phiInsn.getBlockByArgIndex(argIndex);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) {
|
||||
// can't insert move in block with separate instruction
|
||||
// trying previous block
|
||||
List<BlockNode> preds = blockNode.getPredecessors();
|
||||
if (preds.size() == 1) {
|
||||
blockNode = preds.get(0);
|
||||
} else {
|
||||
mth.addWarn("Failed to insert additional move for type inference");
|
||||
return false;
|
||||
}
|
||||
BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex);
|
||||
BlockNode blockNode = checkBlockForInsnInsert(startBlock);
|
||||
if (blockNode == null) {
|
||||
mth.addWarnComment("Failed to insert an additional move for type inference into block " + startBlock);
|
||||
return false;
|
||||
}
|
||||
if (apply) {
|
||||
int regNum = reg.getRegNum();
|
||||
RegisterArg resultArg = reg.duplicate(regNum, null);
|
||||
SSAVar newSsaVar = mth.makeNewSVar(regNum, resultArg);
|
||||
RegisterArg arg = reg.duplicate(regNum, var);
|
||||
|
||||
int regNum = reg.getRegNum();
|
||||
RegisterArg resultArg = reg.duplicate(regNum, null);
|
||||
SSAVar newSsaVar = mth.makeNewSVar(regNum, resultArg);
|
||||
RegisterArg arg = reg.duplicate(regNum, var);
|
||||
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
|
||||
moveInsn.setResult(resultArg);
|
||||
moveInsn.addArg(arg);
|
||||
moveInsn.add(AFlag.SYNTHETIC);
|
||||
blockNode.getInstructions().add(moveInsn);
|
||||
|
||||
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
|
||||
moveInsn.setResult(resultArg);
|
||||
moveInsn.addArg(arg);
|
||||
moveInsn.add(AFlag.SYNTHETIC);
|
||||
blockNode.getInstructions().add(moveInsn);
|
||||
|
||||
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
|
||||
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BlockNode checkBlockForInsnInsert(BlockNode blockNode) {
|
||||
if (blockNode.isSynthetic()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) {
|
||||
// can't insert move in a block with 'separate' instruction => try previous block by simple path
|
||||
List<BlockNode> preds = blockNode.getPredecessors();
|
||||
if (preds.size() == 1) {
|
||||
return checkBlockForInsnInsert(preds.get(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return blockNode;
|
||||
}
|
||||
|
||||
private boolean tryWiderObjects(MethodNode mth, SSAVar var) {
|
||||
Set<ArgType> objTypes = new LinkedHashSet<>();
|
||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||
|
||||
@@ -67,9 +67,10 @@ public class TypeSearch {
|
||||
LOG.warn("Multi-variable search failed in {}", mth);
|
||||
}
|
||||
}
|
||||
|
||||
boolean applySuccess = applyResolvedVars();
|
||||
return searchSuccess && applySuccess;
|
||||
if (searchSuccess) {
|
||||
return applyResolvedVars();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean applyResolvedVars() {
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -22,7 +21,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.TypeUtils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -79,8 +77,9 @@ public final class TypeUpdate {
|
||||
return SAME;
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Applying types, init for {} -> {}", ssaVar, candidateType);
|
||||
updates.forEach(updateEntry -> LOG.debug(" {} -> {}", updateEntry.getType(), updateEntry.getArg()));
|
||||
LOG.debug("Applying types for {} -> {}", ssaVar, candidateType);
|
||||
updates.forEach(updateEntry -> LOG.debug(" {} -> {}, insn: {}",
|
||||
updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn()));
|
||||
}
|
||||
updateInfo.applyUpdates();
|
||||
return CHANGED;
|
||||
@@ -97,13 +96,13 @@ public final class TypeUpdate {
|
||||
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
|
||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||
// don't changed type
|
||||
if (compareResult == TypeCompareEnum.CONFLICT) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType);
|
||||
}
|
||||
return REJECT;
|
||||
if (compareResult == TypeCompareEnum.EQUAL) {
|
||||
return SAME;
|
||||
}
|
||||
return SAME;
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType);
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) {
|
||||
if (Consts.DEBUG) {
|
||||
@@ -282,18 +281,17 @@ public final class TypeUpdate {
|
||||
if (insn.getResult() == null) {
|
||||
return SAME;
|
||||
}
|
||||
if (candidateType.isGeneric() || candidateType.isGenericType()) {
|
||||
if (candidateType.containsTypeVariable()) {
|
||||
InvokeNode invokeNode = (InvokeNode) insn;
|
||||
MethodInfo callMth = invokeNode.getCallMth();
|
||||
if (isAssign(insn, arg)) {
|
||||
// TODO: implement backward type propagation (from result to instance)
|
||||
return SAME;
|
||||
} else {
|
||||
ArgType returnType = root.getMethodGenericReturnType(callMth);
|
||||
ArgType returnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode);
|
||||
if (returnType == null) {
|
||||
return SAME;
|
||||
}
|
||||
ArgType resultGeneric = TypeUtils.replaceClassGenerics(root, candidateType, returnType);
|
||||
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(candidateType, returnType);
|
||||
if (resultGeneric == null) {
|
||||
return SAME;
|
||||
}
|
||||
@@ -414,7 +412,7 @@ public final class TypeUpdate {
|
||||
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
|
||||
if (result == REJECT) {
|
||||
ArgType putType = putArg.getType();
|
||||
if (putType.isTypeKnown() && putType.isObject()) {
|
||||
if (putType.isTypeKnown() && !putType.isPrimitive()) {
|
||||
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
||||
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
||||
// allow wider result (i.e allow put in Object[] any objects)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class BlockInsnPair {
|
||||
private final BlockNode block;
|
||||
private final InsnNode insn;
|
||||
|
||||
public BlockInsnPair(BlockNode block, InsnNode insn) {
|
||||
this.block = block;
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
public BlockNode getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof BlockInsnPair)) {
|
||||
return false;
|
||||
}
|
||||
BlockInsnPair that = (BlockInsnPair) o;
|
||||
return block.equals(that.block) && insn.equals(that.insn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(block, insn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlockInsnPair{" + block + ": " + insn + '}';
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -285,7 +287,19 @@ public class BlockUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BitSet blocksToBitSet(MethodNode mth, List<BlockNode> blocks) {
|
||||
public static BitSet newBlocksBitSet(MethodNode mth) {
|
||||
return new BitSet(mth.getBasicBlocks().size());
|
||||
}
|
||||
|
||||
public static BitSet copyBlocksBitSet(MethodNode mth, BitSet bitSet) {
|
||||
BitSet copy = new BitSet(mth.getBasicBlocks().size());
|
||||
if (!bitSet.isEmpty()) {
|
||||
copy.or(bitSet);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static BitSet blocksToBitSet(MethodNode mth, Collection<BlockNode> blocks) {
|
||||
BitSet bs = new BitSet(mth.getBasicBlocks().size());
|
||||
for (BlockNode block : blocks) {
|
||||
bs.set(block.getId());
|
||||
@@ -293,8 +307,16 @@ public class BlockUtils {
|
||||
return bs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BlockNode bitSetToOneBlock(MethodNode mth, BitSet bs) {
|
||||
if (bs == null || bs.cardinality() != 1) {
|
||||
return null;
|
||||
}
|
||||
return mth.getBasicBlocks().get(bs.nextSetBit(0));
|
||||
}
|
||||
|
||||
public static List<BlockNode> bitSetToBlocks(MethodNode mth, BitSet bs) {
|
||||
if (bs == null) {
|
||||
if (bs == null || bs == EmptyBitSet.EMPTY) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int size = bs.cardinality();
|
||||
@@ -649,4 +671,102 @@ public class BlockUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Map<BlockNode, BitSet> calcPostDominance(MethodNode mth) {
|
||||
return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getExitBlocks().get(0));
|
||||
}
|
||||
|
||||
public static Map<BlockNode, BitSet> calcPartialPostDominance(MethodNode mth, Collection<BlockNode> blockNodes, BlockNode exitBlock) {
|
||||
int blocksCount = mth.getBasicBlocks().size();
|
||||
Map<BlockNode, BitSet> map = new HashMap<>(blocksCount);
|
||||
|
||||
BitSet initSet = new BitSet(blocksCount);
|
||||
for (BlockNode block : blockNodes) {
|
||||
initSet.set(block.getId());
|
||||
}
|
||||
|
||||
for (BlockNode block : blockNodes) {
|
||||
BitSet postDoms = new BitSet(blocksCount);
|
||||
postDoms.or(initSet);
|
||||
map.put(block, postDoms);
|
||||
}
|
||||
BitSet exitBitSet = map.get(exitBlock);
|
||||
exitBitSet.clear();
|
||||
exitBitSet.set(exitBlock.getId());
|
||||
|
||||
BitSet domSet = new BitSet(blocksCount);
|
||||
boolean changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (BlockNode block : blockNodes) {
|
||||
if (block == exitBlock) {
|
||||
continue;
|
||||
}
|
||||
BitSet d = map.get(block);
|
||||
if (!changed) {
|
||||
domSet.clear();
|
||||
domSet.or(d);
|
||||
}
|
||||
for (BlockNode scc : block.getSuccessors()) {
|
||||
BitSet scPDoms = map.get(scc);
|
||||
if (scPDoms != null) {
|
||||
d.and(scPDoms);
|
||||
}
|
||||
}
|
||||
d.set(block.getId());
|
||||
if (!changed && !d.equals(domSet)) {
|
||||
changed = true;
|
||||
map.put(block, d);
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
blockNodes.forEach(block -> {
|
||||
BitSet postDoms = map.get(block);
|
||||
postDoms.clear(block.getId());
|
||||
if (postDoms.isEmpty()) {
|
||||
map.put(block, EmptyBitSet.EMPTY);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block) {
|
||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
||||
if (oneSuccessor != null) {
|
||||
return oneSuccessor;
|
||||
}
|
||||
return calcImmediatePostDominator(mth, block, calcPostDominance(mth));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BlockNode calcPartialImmediatePostDominator(MethodNode mth, BlockNode block,
|
||||
Collection<BlockNode> blockNodes, BlockNode exitBlock) {
|
||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
||||
if (oneSuccessor != null) {
|
||||
return oneSuccessor;
|
||||
}
|
||||
Map<BlockNode, BitSet> pDomsMap = calcPartialPostDominance(mth, blockNodes, exitBlock);
|
||||
return calcImmediatePostDominator(mth, block, pDomsMap);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BlockNode calcImmediatePostDominator(MethodNode mth, BlockNode block, Map<BlockNode, BitSet> postDomsMap) {
|
||||
BlockNode oneSuccessor = Utils.getOne(block.getSuccessors());
|
||||
if (oneSuccessor != null) {
|
||||
return oneSuccessor;
|
||||
}
|
||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
||||
BitSet postDoms = postDomsMap.get(block);
|
||||
BitSet bs = copyBlocksBitSet(mth, postDoms);
|
||||
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
|
||||
BlockNode pdomBlock = basicBlocks.get(i);
|
||||
BitSet pdoms = postDomsMap.get(pdomBlock);
|
||||
if (pdoms != null) {
|
||||
bs.andNot(pdoms);
|
||||
}
|
||||
}
|
||||
return bitSetToOneBlock(mth, bs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
/**
|
||||
* Check invariants and information consistency for registers and SSA variables
|
||||
*/
|
||||
@@ -86,7 +88,8 @@ public class DebugChecks {
|
||||
List<RegisterArg> useList = sVar.getUseList();
|
||||
boolean assignReg = insn.getResult() == reg;
|
||||
if (!assignReg && !Utils.containsInListByRef(useList, reg)) {
|
||||
throw new JadxRuntimeException("Incorrect use list in ssa var: " + sVar + ", register not listed.\n insn: " + insn);
|
||||
throw new JadxRuntimeException("Incorrect use list in ssa var: " + sVar + ", register not listed."
|
||||
+ NL + " insn: " + insn);
|
||||
}
|
||||
for (RegisterArg useArg : useList) {
|
||||
checkRegisterArg(mth, useArg);
|
||||
@@ -107,8 +110,8 @@ public class DebugChecks {
|
||||
}
|
||||
BlockNode parentInsnBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
|
||||
if (parentInsnBlock == null) {
|
||||
parentInsnBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
|
||||
throw new JadxRuntimeException("Parent insn not found in blocks tree for: " + reg + ",\n insn: " + parentInsn);
|
||||
throw new JadxRuntimeException("Parent insn not found in blocks tree for: " + reg
|
||||
+ NL + " insn: " + parentInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -30,6 +31,8 @@ import jadx.core.dex.visitors.regions.TracedRegionVisitor;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
@Deprecated
|
||||
@TestOnly
|
||||
public class DebugUtils {
|
||||
@@ -97,7 +100,7 @@ public class DebugUtils {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.startLine('|').add(mth.toString());
|
||||
printRegion(mth, region, cw, "| ", printInsns);
|
||||
LOG.debug("\n{}", cw.finish().getCodeStr());
|
||||
LOG.debug("{}{}", NL, cw.finish().getCodeStr());
|
||||
}
|
||||
|
||||
private static void printRegion(MethodNode mth, IRegion region, CodeWriter cw, String indent, boolean printInsns) {
|
||||
@@ -125,7 +128,7 @@ public class DebugUtils {
|
||||
ig.makeInsn(insn, code);
|
||||
String codeStr = code.finish().getCodeStr();
|
||||
|
||||
List<String> insnStrings = Arrays.stream(codeStr.split(CodeWriter.NL))
|
||||
List<String> insnStrings = Arrays.stream(codeStr.split(NL))
|
||||
.filter(StringUtils::notBlank)
|
||||
.map(s -> "|> " + s)
|
||||
.collect(Collectors.toList());
|
||||
@@ -147,7 +150,7 @@ public class DebugUtils {
|
||||
|
||||
private static void printWithAttributes(CodeWriter cw, String indent, String codeStr, IAttributeNode attrNode) {
|
||||
String str = attrNode.isAttrStorageEmpty() ? codeStr : codeStr + ' ' + attrNode.getAttributesString();
|
||||
List<String> attrStrings = Arrays.stream(str.split(CodeWriter.NL))
|
||||
List<String> attrStrings = Arrays.stream(str.split(NL))
|
||||
.filter(StringUtils::notBlank)
|
||||
.collect(Collectors.toList());
|
||||
Iterator<String> it = attrStrings.iterator();
|
||||
@@ -159,4 +162,11 @@ public class DebugUtils {
|
||||
cw.startLine(indent).add("|+ ").add(it.next());
|
||||
}
|
||||
}
|
||||
|
||||
public static void printMap(Map<?, ?> map, String desc) {
|
||||
LOG.debug("Map {} (size = {}):", desc, map.size());
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
LOG.debug(" {}: {}", entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,12 @@ public final class ImmutableList<E> implements List<E>, RandomAccess {
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
for (Object obj : c) {
|
||||
if (!contains(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -20,6 +20,8 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
|
||||
/**
|
||||
* Helper class for correct instructions removing,
|
||||
* can be used while iterating over instructions list
|
||||
@@ -130,10 +132,10 @@ public class InsnRemover {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG) { // TODO: enable this
|
||||
throw new JadxRuntimeException("Can't remove SSA var, still in use, count: " + useCount
|
||||
+ ", list:\n " + ssaVar.getUseList().stream()
|
||||
throw new JadxRuntimeException("Can't remove SSA var, still in use, count: " + useCount + ", list:"
|
||||
+ NL + " " + ssaVar.getUseList().stream()
|
||||
.map(arg -> arg + " from " + arg.getParentInsn())
|
||||
.collect(Collectors.joining("\n ")));
|
||||
.collect(Collectors.joining(NL + " ")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +170,10 @@ public class InsnRemover {
|
||||
}
|
||||
}
|
||||
if (!found && Consts.DEBUG) { // TODO: enable this
|
||||
throw new JadxRuntimeException("Can't remove insn:\n " + rem
|
||||
+ "\nnot found in list:\n " + Utils.listToString(insns, "\n "));
|
||||
throw new JadxRuntimeException("Can't remove insn:"
|
||||
+ NL + " " + rem
|
||||
+ NL + " not found in list:"
|
||||
+ NL + " " + Utils.listToString(insns, NL + " "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class SmaliUtils {
|
||||
try {
|
||||
Path path = dex.getDexFile().getPath();
|
||||
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null);
|
||||
DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset);
|
||||
DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0);
|
||||
getSmaliCode(dexBackedClassDef, stringWriter);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.CallMthInterface;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeUtils {
|
||||
/**
|
||||
* Replace generic types in {@code typeWithGeneric} using instance types
|
||||
* <br>
|
||||
* Example:
|
||||
* <ul>
|
||||
* <li>{@code instanceType: Set<String>}
|
||||
* <li>{@code typeWithGeneric: Iterator<E>}
|
||||
* <li>{@code return: Iterator<String>}
|
||||
* </ul>
|
||||
*/
|
||||
@Nullable
|
||||
public static ArgType replaceClassGenerics(RootNode root, ArgType instanceType, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
if (instanceType.isGeneric()) {
|
||||
List<GenericInfo> generics = root.getClassGenerics(instanceType);
|
||||
if (generics.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
ArgType[] actualTypes = instanceType.getGenericTypes();
|
||||
if (actualTypes == null) {
|
||||
return null;
|
||||
}
|
||||
int genericParamsCount = actualTypes.length;
|
||||
if (genericParamsCount != generics.size()) {
|
||||
return null;
|
||||
}
|
||||
Map<ArgType, ArgType> replaceMap = new HashMap<>(genericParamsCount);
|
||||
for (int i = 0; i < genericParamsCount; i++) {
|
||||
ArgType actualType = actualTypes[i];
|
||||
ArgType genericType = generics.get(i).getGenericType();
|
||||
replaceMap.put(genericType, actualType);
|
||||
}
|
||||
return replaceGenericUsingTypeMap(typeWithGeneric, replaceMap);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ArgType replaceMethodGenerics(RootNode root, InsnNode invokeInsn, ArgType typeWithGeneric) {
|
||||
if (typeWithGeneric == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(invokeInsn instanceof CallMthInterface)) {
|
||||
throw new JadxRuntimeException("Expected CallMthInterface, got: " + invokeInsn.getClass());
|
||||
}
|
||||
CallMthInterface callInsn = (CallMthInterface) invokeInsn;
|
||||
MethodInfo mthInfo = callInsn.getCallMth();
|
||||
List<ArgType> methodArgTypes = root.getMethodArgTypes(mthInfo);
|
||||
if (methodArgTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int firstArgOffset = callInsn.getFirstArgOffset();
|
||||
int argsCount = methodArgTypes.size();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
ArgType methodArgType = methodArgTypes.get(i);
|
||||
InsnArg insnArg = invokeInsn.getArg(i + firstArgOffset);
|
||||
ArgType insnType = insnArg.getType();
|
||||
if (methodArgType.equals(typeWithGeneric)) {
|
||||
return insnType;
|
||||
}
|
||||
}
|
||||
// TODO build complete map for type variables
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArgType replaceGenericUsingTypeMap(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
|
||||
if (replaceType.isGenericType()) {
|
||||
return replaceMap.get(replaceType);
|
||||
}
|
||||
|
||||
ArgType wildcardType = replaceType.getWildcardType();
|
||||
if (wildcardType != null && wildcardType.containsGenericType()) {
|
||||
ArgType newWildcardType = replaceGenericUsingTypeMap(wildcardType, replaceMap);
|
||||
if (newWildcardType == null) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.wildcard(newWildcardType, replaceType.getWildcardBound());
|
||||
}
|
||||
|
||||
ArgType[] genericTypes = replaceType.getGenericTypes();
|
||||
if (replaceType.isGeneric() && genericTypes != null && genericTypes.length != 0) {
|
||||
int size = genericTypes.length;
|
||||
ArgType[] newTypes = new ArgType[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
ArgType genericType = genericTypes[i];
|
||||
ArgType type = replaceGenericUsingTypeMap(genericType, replaceMap);
|
||||
if (type == null) {
|
||||
type = genericType;
|
||||
}
|
||||
newTypes[i] = type;
|
||||
}
|
||||
return ArgType.generic(replaceType.getObject(), newTypes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -247,6 +247,13 @@ public class Utils {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
public static <T> T getOrElse(@Nullable T obj, T defaultObj) {
|
||||
if (obj == null) {
|
||||
return defaultObj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static <T> boolean isEmpty(Collection<T> col) {
|
||||
return col == null || col.isEmpty();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.codegen.CodeWriter.NL;
|
||||
import static jadx.core.utils.files.FileUtils.isApkFile;
|
||||
import static jadx.core.utils.files.FileUtils.isZipDexFile;
|
||||
|
||||
@@ -197,7 +198,7 @@ public class InputFile {
|
||||
}
|
||||
return pathList;
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
|
||||
throw new DecodeException("java class to dex conversion error:" + NL + " " + e.getMessage(), e);
|
||||
} finally {
|
||||
if (j2d.isError()) {
|
||||
LOG.warn("dx message: {}", j2d.getDxErrors());
|
||||
|
||||
@@ -24,10 +24,10 @@ class ArgTypeTest {
|
||||
@Test
|
||||
void testContainsGenericType() {
|
||||
ArgType wildcard = ArgType.wildcard(ArgType.genericType("T"), ArgType.WildcardBound.SUPER);
|
||||
assertTrue(wildcard.containsGenericType());
|
||||
assertTrue(wildcard.containsTypeVariable());
|
||||
|
||||
ArgType type = ArgType.generic("java.lang.List", wildcard);
|
||||
assertTrue(type.containsGenericType());
|
||||
assertTrue(type.containsTypeVariable());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,28 +5,38 @@ import java.util.Collections;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.NotYetImplementedExtension;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.BOOLEAN;
|
||||
import static jadx.core.dex.instructions.args.ArgType.BYTE;
|
||||
import static jadx.core.dex.instructions.args.ArgType.CHAR;
|
||||
import static jadx.core.dex.instructions.args.ArgType.CLASS;
|
||||
import static jadx.core.dex.instructions.args.ArgType.INT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.NARROW;
|
||||
import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL;
|
||||
import static jadx.core.dex.instructions.args.ArgType.OBJECT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.STRING;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_ARRAY;
|
||||
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
|
||||
import static jadx.core.dex.instructions.args.ArgType.array;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static jadx.core.dex.instructions.args.ArgType.generic;
|
||||
import static jadx.core.dex.instructions.args.ArgType.object;
|
||||
import static jadx.core.dex.instructions.args.ArgType.wildcard;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(NotYetImplementedExtension.class)
|
||||
public class TypeCompareTest {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeCompareTest.class);
|
||||
|
||||
private TypeCompare compare;
|
||||
|
||||
@BeforeEach
|
||||
@@ -65,23 +75,58 @@ public class TypeCompareTest {
|
||||
|
||||
firstIsNarrow(array(OBJECT), OBJECT);
|
||||
firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT));
|
||||
firstIsNarrow(array(STRING), array(UNKNOWN_OBJECT));
|
||||
firstIsNarrow(array(STRING), array(OBJECT));
|
||||
|
||||
firstIsNarrow(UNKNOWN_ARRAY, OBJECT);
|
||||
|
||||
firstIsNarrow(array(BYTE), OBJECT);
|
||||
firstIsNarrow(array(array(BYTE)), array(OBJECT));
|
||||
|
||||
check(array(OBJECT), array(INT), TypeCompareEnum.CONFLICT);
|
||||
|
||||
ArgType integerType = object("java.lang.Integer");
|
||||
check(array(OBJECT), array(integerType), TypeCompareEnum.WIDER);
|
||||
check(array(INT), array(integerType), TypeCompareEnum.CONFLICT);
|
||||
check(array(INT), array(INT), TypeCompareEnum.EQUAL);
|
||||
|
||||
ArgType wildClass = generic(CLASS, wildcard());
|
||||
check(array(wildClass), array(CLASS), TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(array(CLASS), array(wildClass), TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareGenerics() {
|
||||
ArgType mapCls = ArgType.object("java.util.Map");
|
||||
ArgType setCls = ArgType.object("java.util.Set");
|
||||
ArgType mapCls = object("java.util.Map");
|
||||
ArgType setCls = object("java.util.Set");
|
||||
|
||||
ArgType keyType = ArgType.genericType("K");
|
||||
ArgType valueType = ArgType.genericType("V");
|
||||
ArgType mapGeneric = ArgType.generic(mapCls.getObject(), keyType, valueType);
|
||||
|
||||
check(mapGeneric, mapCls, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(mapCls, mapGeneric, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
|
||||
check(mapCls, setCls, TypeCompareEnum.CONFLICT);
|
||||
|
||||
ArgType setGeneric = ArgType.generic(setCls.getObject(), valueType);
|
||||
ArgType setWildcard = ArgType.generic(setCls.getObject(), ArgType.wildcard());
|
||||
|
||||
check(setWildcard, setGeneric, TypeCompareEnum.CONFLICT);
|
||||
check(setWildcard, setCls, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
// TODO implement compare for wildcard with bounds
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareWildCards() {
|
||||
ArgType clsWildcard = generic(CLASS.getObject(), wildcard());
|
||||
ArgType clsExtendedWildcard = generic(CLASS.getObject(), wildcard(STRING, WildcardBound.EXTENDS));
|
||||
check(clsWildcard, clsExtendedWildcard, TypeCompareEnum.WIDER);
|
||||
|
||||
ArgType listWildcard = generic(CLASS.getObject(), wildcard(object("java.util.List"), WildcardBound.EXTENDS));
|
||||
ArgType collWildcard = generic(CLASS.getObject(), wildcard(object("java.util.Collection"), WildcardBound.EXTENDS));
|
||||
check(listWildcard, collWildcard, TypeCompareEnum.NARROW);
|
||||
|
||||
ArgType collSuperWildcard = generic(CLASS.getObject(), wildcard(object("java.util.Collection"), WildcardBound.SUPER));
|
||||
check(collSuperWildcard, listWildcard, TypeCompareEnum.CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,10 +144,7 @@ public class TypeCompareTest {
|
||||
tType.setExtendTypes(Collections.singletonList(ArgType.STRING));
|
||||
|
||||
check(tType, ArgType.STRING, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(ArgType.STRING, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
|
||||
check(tType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
|
||||
check(ArgType.OBJECT, tType, TypeCompareEnum.WIDER_BY_GENERIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,18 +153,31 @@ public class TypeCompareTest {
|
||||
ArgType vType = ArgType.genericType("V");
|
||||
// TODO: use extend types from generic declaration for more strict checks
|
||||
check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
|
||||
check(ArgType.STRING, vType, TypeCompareEnum.CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareOuterGenerics() {
|
||||
ArgType hashMapType = object("java.util.HashMap");
|
||||
ArgType innerEntrySetType = object("EntrySet");
|
||||
ArgType firstInstance = ArgType.outerGeneric(generic(hashMapType, STRING, STRING), innerEntrySetType);
|
||||
ArgType secondInstance = ArgType.outerGeneric(generic(hashMapType, OBJECT, OBJECT), innerEntrySetType);
|
||||
|
||||
check(firstInstance, secondInstance, TypeCompareEnum.NARROW);
|
||||
}
|
||||
|
||||
private void firstIsNarrow(ArgType first, ArgType second) {
|
||||
check(first, second, TypeCompareEnum.NARROW);
|
||||
// reverse
|
||||
check(second, first, TypeCompareEnum.WIDER);
|
||||
}
|
||||
|
||||
private void check(ArgType first, ArgType second, TypeCompareEnum expectedResult) {
|
||||
TypeCompareEnum result = compare.compareTypes(first, second);
|
||||
assertThat("Compare '" + first + "' vs '" + second + '\'',
|
||||
result, is(expectedResult));
|
||||
LOG.debug("Compare: '{}' and '{}', expect: '{}'", first, second, expectedResult);
|
||||
|
||||
assertThat(compare.compareTypes(first, second))
|
||||
.as("Compare '%s' and '%s'", first, second)
|
||||
.isEqualTo(expectedResult);
|
||||
|
||||
assertThat(compare.compareTypes(second, first))
|
||||
.as("Compare '%s' and '%s'", second, first)
|
||||
.isEqualTo(expectedResult.invert());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -10,7 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
@@ -25,17 +24,16 @@ class TypeUtilsTest {
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
root = new RootNode(new JadxArgs());
|
||||
root.load(Collections.emptyList());
|
||||
root.initClassPath();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceGenericsWithWildcards() {
|
||||
// check classpath graph
|
||||
List<GenericInfo> classGenerics = root.getClassGenerics(ArgType.object("java.util.ArrayList"));
|
||||
List<GenericTypeParameter> classGenerics = root.getTypeUtils().getClassGenerics(ArgType.object("java.util.ArrayList"));
|
||||
assertThat(classGenerics, hasSize(1));
|
||||
GenericInfo genericInfo = classGenerics.get(0);
|
||||
assertThat(genericInfo.getGenericType(), is(ArgType.genericType("E")));
|
||||
GenericTypeParameter genericInfo = classGenerics.get(0);
|
||||
assertThat(genericInfo.getTypeVariable(), is(ArgType.genericType("E")));
|
||||
assertThat(genericInfo.getExtendsList(), hasSize(0));
|
||||
|
||||
// prepare input
|
||||
@@ -46,7 +44,7 @@ class TypeUtilsTest {
|
||||
LOG.debug("generic: {}", generic);
|
||||
|
||||
// replace
|
||||
ArgType result = TypeUtils.replaceClassGenerics(root, instanceType, generic);
|
||||
ArgType result = root.getTypeUtils().replaceClassGenerics(instanceType, generic);
|
||||
LOG.debug("result: {}", result);
|
||||
|
||||
ArgType expected = ArgType.generic("java.util.List", ArgType.wildcard(ArgType.OBJECT, ArgType.WildcardBound.SUPER));
|
||||
|
||||
@@ -10,6 +10,10 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
|
||||
super(code, JadxCodeAssertions.class);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions containsOne(String substring) {
|
||||
return countString(1, substring);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions countString(int count, String substring) {
|
||||
isNotNull();
|
||||
int actualCount = TestUtils.count(actual, substring);
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
package jadx.tests.functional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.STRING;
|
||||
import static jadx.core.dex.instructions.args.ArgType.object;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JadxClasspathTest {
|
||||
|
||||
private static final String JAVA_LANG_EXCEPTION = "java.lang.Exception";
|
||||
private static final String JAVA_LANG_THROWABLE = "java.lang.Throwable";
|
||||
|
||||
private DexNode dex;
|
||||
private RootNode root;
|
||||
private ClspGraph clsp;
|
||||
|
||||
@BeforeEach
|
||||
public void initClsp() throws IOException, DecodeException {
|
||||
clsp = new ClspGraph();
|
||||
clsp.load();
|
||||
dex = mock(DexNode.class);
|
||||
RootNode rootNode = mock(RootNode.class);
|
||||
when(rootNode.getClsp()).thenReturn(clsp);
|
||||
when(dex.root()).thenReturn(rootNode);
|
||||
public void initClsp() {
|
||||
this.root = new RootNode(new JadxArgs());
|
||||
this.root.load(Collections.emptyList());
|
||||
this.root.initClassPath();
|
||||
this.clsp = root.getClsp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -44,9 +39,9 @@ public class JadxClasspathTest {
|
||||
assertTrue(clsp.isImplements(JAVA_LANG_EXCEPTION, JAVA_LANG_THROWABLE));
|
||||
assertFalse(clsp.isImplements(JAVA_LANG_THROWABLE, JAVA_LANG_EXCEPTION));
|
||||
|
||||
assertFalse(ArgType.isCastNeeded(dex, objExc, objThr));
|
||||
assertTrue(ArgType.isCastNeeded(dex, objThr, objExc));
|
||||
assertFalse(ArgType.isCastNeeded(root, objExc, objThr));
|
||||
assertTrue(ArgType.isCastNeeded(root, objThr, objExc));
|
||||
|
||||
assertTrue(ArgType.isCastNeeded(dex, ArgType.OBJECT, STRING));
|
||||
assertTrue(ArgType.isCastNeeded(root, ArgType.OBJECT, STRING));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.ArgType.WildcardBound;
|
||||
import jadx.core.dex.nodes.GenericInfo;
|
||||
import jadx.core.dex.nodes.GenericTypeParameter;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
|
||||
import static jadx.core.dex.instructions.args.ArgType.INT;
|
||||
@@ -92,12 +92,12 @@ class SignatureParserTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void checkGenerics(String g, Object... objs) {
|
||||
List<GenericInfo> genericsList = new SignatureParser(g).consumeGenericMap();
|
||||
List<GenericInfo> expectedList = new ArrayList<>();
|
||||
List<GenericTypeParameter> genericsList = new SignatureParser(g).consumeGenericTypeParameters();
|
||||
List<GenericTypeParameter> expectedList = new ArrayList<>();
|
||||
for (int i = 0; i < objs.length; i += 2) {
|
||||
ArgType generic = genericType((String) objs[i]);
|
||||
List<ArgType> list = (List<ArgType>) objs[i + 1];
|
||||
expectedList.add(new GenericInfo(generic, list));
|
||||
expectedList.add(new GenericTypeParameter(generic, list));
|
||||
}
|
||||
assertThat(genericsList, is(expectedList));
|
||||
}
|
||||
@@ -122,7 +122,7 @@ class SignatureParserTest {
|
||||
|
||||
@Test
|
||||
public void testBadGenericMap() {
|
||||
List<GenericInfo> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericMap();
|
||||
List<GenericTypeParameter> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericTypeParameters();
|
||||
assertThat(list, hasSize(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class TestElseIf extends IntegrationTest {
|
||||
assertThat(code, containsOne("r = 1;"));
|
||||
assertThat(code, containsOne("r = -1;"));
|
||||
// no ternary operator
|
||||
assertThat(code, not(containsString("?")));
|
||||
assertThat(code, not(containsString(":")));
|
||||
assertThat(code, not(containsString(" ? ")));
|
||||
assertThat(code, not(containsString(" : ")));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class TestConditions19 extends IntegrationTest {
|
||||
public class TestInnerAssign extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private String result;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user