提交 459f12d6 编写于 作者: S Skylot

fix: several improvements for generics and type inference

- support 'extends' for generic type variables
- insert cast instructions to help type inference (#956)
- correct move instructions insertion (to resolve types in PHI)
Signed-off-by: NSkylot <skylot@gmail.com>
上级 bcd6e537
package jadx.core.clsp;
package jadx.cli.clst;
import java.io.IOException;
import java.nio.file.Path;
......@@ -16,6 +16,7 @@ import jadx.api.JadxArgs;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.clsp.ClsSet;
import jadx.core.dex.nodes.RootNode;
/**
......@@ -50,6 +51,7 @@ public class ConvertToClsSet {
ClsSet set = new ClsSet(root);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
LOG.info("done");
}
......
......@@ -10,6 +10,7 @@ public class Consts {
public static final String CLASS_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class";
public static final String CLASS_THROWABLE = "java.lang.Throwable";
public static final String CLASS_EXCEPTION = "java.lang.Exception";
public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
......
......@@ -32,7 +32,6 @@ 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.GenericTypeParameter;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
......@@ -40,7 +39,6 @@ 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;
/**
......@@ -139,7 +137,7 @@ public class ClsSet {
ArgType genericRetType = mth.getReturnType();
boolean varArgs = accessFlags.isVarArgs();
List<ArgType> throwList = mth.getThrows();
List<GenericTypeParameter> typeParameters = mth.getTypeParameters();
List<ArgType> typeParameters = mth.getTypeParameters();
// add only methods with additional info
if (varArgs
|| notEmpty(throwList)
......@@ -186,7 +184,7 @@ public class ClsSet {
return cls;
}
void save(Path path) throws IOException {
public void save(Path path) throws IOException {
FileUtils.makeDirsForFile(path);
String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) {
......@@ -225,7 +223,7 @@ public class ClsSet {
}
}
public void save(OutputStream output) throws IOException {
private void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
......@@ -239,7 +237,7 @@ public class ClsSet {
}
for (ClspClass cls : classes) {
writeArgTypesArray(out, cls.getParents(), names);
writeGenericTypeParameters(out, cls.getTypeParameters(), names);
writeArgTypesList(out, cls.getTypeParameters(), names);
List<ClspMethod> methods = cls.getSortedMethodsList();
out.writeShort(methods.size());
for (ClspMethod method : methods) {
......@@ -250,20 +248,6 @@ public class ClsSet {
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
}
private static void writeGenericTypeParameters(DataOutputStream out,
List<GenericTypeParameter> typeParameters,
Map<String, ClspClass> names) throws IOException {
if (isEmpty(typeParameters)) {
out.writeByte(0);
} else {
writeUnsignedByte(out, typeParameters.size());
for (GenericTypeParameter typeParameter : typeParameters) {
writeArgType(out, typeParameter.getTypeVariable(), names);
writeArgTypesList(out, typeParameter.getExtendsList(), names);
}
}
}
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
MethodInfo methodInfo = method.getMethodInfo();
writeString(out, methodInfo.getName());
......@@ -272,7 +256,7 @@ public class ClsSet {
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
writeArgType(out, method.getReturnType(), names);
writeGenericTypeParameters(out, method.getTypeParameters(), names);
writeArgTypesList(out, method.getTypeParameters(), names);
out.writeBoolean(method.isVarArg());
writeArgTypesList(out, method.getThrows(), names);
}
......@@ -323,11 +307,11 @@ public class ClsSet {
} else if (argType.isGeneric()) {
out.writeByte(TypeEnum.GENERIC.ordinal());
out.writeInt(getCls(argType, names).getId());
ArgType[] types = argType.getGenericTypes();
writeArgTypesArray(out, types, names);
writeArgTypesList(out, argType.getGenericTypes(), names);
} else if (argType.isGenericType()) {
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
writeString(out, argType.getObject());
writeArgTypesList(out, argType.getExtendTypes(), names);
} else if (argType.isObject()) {
out.writeByte(TypeEnum.OBJECT.ordinal());
out.writeInt(getCls(argType, names).getId());
......@@ -380,26 +364,12 @@ public class ClsSet {
ClspClass nClass = classes[i];
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
nClass.setParents(readArgTypesArray(in));
nClass.setTypeParameters(readGenericTypeParameters(in));
nClass.setTypeParameters(readArgTypesList(in));
nClass.setMethods(readClsMethods(in, clsInfo));
}
}
}
private List<GenericTypeParameter> readGenericTypeParameters(DataInputStream in) throws IOException {
int count = readUnsignedByte(in);
if (count == 0) {
return Collections.emptyList();
}
List<GenericTypeParameter> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType typeVariable = readArgType(in);
List<ArgType> extendsList = readArgTypesList(in);
list.add(new GenericTypeParameter(typeVariable, extendsList));
}
return list;
}
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int mCount = in.readShort();
List<ClspMethod> methods = new ArrayList<>(mCount);
......@@ -421,7 +391,7 @@ public class ClsSet {
if (Objects.equals(genericRetType, retType)) {
genericRetType = retType;
}
List<GenericTypeParameter> typeParameters = readGenericTypeParameters(in);
List<ArgType> typeParameters = readArgTypesList(in);
boolean varArgs = in.readBoolean();
List<ArgType> throwList = readArgTypesList(in);
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
......@@ -482,20 +452,12 @@ public class ClsSet {
case GENERIC:
ArgType clsType = classes[in.readInt()].getClsType();
int typeLength = readUnsignedByte(in);
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
}
return ArgType.generic(clsType, generics);
return ArgType.generic(clsType, readArgTypesList(in));
case GENERIC_TYPE_VARIABLE:
return ArgType.genericType(readString(in));
String typeVar = readString(in);
List<ArgType> extendTypes = readArgTypesList(in);
return ArgType.genericType(typeVar, extendTypes);
case OBJECT:
return classes[in.readInt()].getClsType();
......
......@@ -8,7 +8,6 @@ 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
......@@ -19,7 +18,7 @@ public class ClspClass {
private final int id;
private ArgType[] parents;
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<GenericTypeParameter> typeParameters = Collections.emptyList();
private List<ArgType> typeParameters = Collections.emptyList();
public ClspClass(ArgType clsType, int id) {
this.clsType = clsType;
......@@ -69,11 +68,11 @@ public class ClspClass {
setMethodsMap(map);
}
public List<GenericTypeParameter> getTypeParameters() {
public List<ArgType> getTypeParameters() {
return typeParameters;
}
public void setTypeParameters(List<GenericTypeParameter> typeParameters) {
public void setTypeParameters(List<ArgType> typeParameters) {
this.typeParameters = typeParameters;
}
......
......@@ -7,7 +7,6 @@ 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;
......@@ -19,13 +18,13 @@ 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> typeParameters;
private final List<ArgType> throwList;
private final boolean varArg;
public ClspMethod(MethodInfo methodInfo,
List<ArgType> argTypes, ArgType returnType,
List<GenericTypeParameter> typeParameters,
List<ArgType> typeParameters,
boolean varArgs, List<ArgType> throwList) {
this.methodInfo = methodInfo;
this.argTypes = argTypes;
......@@ -59,7 +58,7 @@ public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
}
@Override
public List<GenericTypeParameter> getTypeParameters() {
public List<ArgType> getTypeParameters() {
return typeParameters;
}
......
......@@ -5,7 +5,6 @@ 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 {
......@@ -32,7 +31,7 @@ public class SimpleMethodDetails implements IMethodDetails {
}
@Override
public List<GenericTypeParameter> getTypeParameters() {
public List<ArgType> getTypeParameters() {
return Collections.emptyList();
}
......
......@@ -36,7 +36,6 @@ import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.GenericTypeParameter;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
......@@ -189,23 +188,22 @@ public class ClassGen {
}
}
public boolean addGenericTypeParameters(CodeWriter code, List<GenericTypeParameter> generics, boolean classDeclaration) {
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (GenericTypeParameter genericInfo : generics) {
for (ArgType genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
ArgType type = genericInfo.getTypeVariable();
if (type.isGenericType()) {
code.add(type.getObject());
if (genericInfo.isGenericType()) {
code.add(genericInfo.getObject());
} else {
useClass(code, type);
useClass(code, genericInfo);
}
List<ArgType> list = genericInfo.getExtendsList();
List<ArgType> list = genericInfo.getExtendTypes();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
......@@ -517,15 +515,15 @@ public class ClassGen {
}
useClass(code, ClassInfo.fromType(cls.root(), type));
ArgType[] generics = type.getGenericTypes();
List<ArgType> generics = type.getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
int len = generics.size();
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType gt = generics.get(i);
ArgType wt = gt.getWildcardType();
if (wt != null) {
ArgType.WildcardBound bound = gt.getWildcardBound();
......
......@@ -185,7 +185,7 @@ public class MethodGen {
CodeVar var;
if (ssaVar == null) {
// null for abstract or interface methods
var = CodeVar.fromMthArg(mthArg);
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
} else {
var = ssaVar.getCodeVar();
}
......
......@@ -72,11 +72,13 @@ public class NameGen {
}
public String assignArg(CodeVar var) {
String name = makeArgName(var);
if (fallback) {
return name;
return getFallbackName(var);
}
name = getUniqueVarName(name);
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = getUniqueVarName(makeArgName(var));
var.setName(name);
return name;
}
......@@ -118,24 +120,17 @@ public class NameGen {
}
private String makeArgName(CodeVar var) {
if (fallback) {
return getFallbackName(var);
}
if (var.isThis()) {
return RegisterArg.THIS_ARG_NAME;
}
String name = var.getName();
String varName = name != null ? name : guessName(var);
if (NameMapper.isReserved(varName)) {
varName = varName + 'R';
if (name == null) {
name = guessName(var);
}
if (!NameMapper.isValidAndPrintable(varName)) {
varName = getFallbackName(var);
if (!NameMapper.isValidAndPrintable(name)) {
name = getFallbackName(var);
}
if (Consts.DEBUG) {
varName += '_' + getFallbackName(var);
name += '_' + getFallbackName(var);
}
return varName;
return name;
}
private String getFallbackName(CodeVar var) {
......
......@@ -67,6 +67,7 @@ public enum AFlag {
*/
EXPLICIT_PRIMITIVE_TYPE,
EXPLICIT_CAST,
SOFT_CAST, // synthetic cast to help type inference
INCONSISTENT_CODE, // warning about incorrect decompilation
}
package jadx.core.dex.attributes.nodes;
import java.util.Arrays;
import java.util.List;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
public class GenericInfoAttr implements IAttribute {
private final ArgType[] genericTypes;
private final List<ArgType> genericTypes;
private boolean explicit;
public GenericInfoAttr(ArgType[] genericTypes) {
public GenericInfoAttr(List<ArgType> genericTypes) {
this.genericTypes = genericTypes;
}
public ArgType[] getGenericTypes() {
public List<ArgType> getGenericTypes() {
return genericTypes;
}
......@@ -33,6 +33,6 @@ public class GenericInfoAttr implements IAttribute {
@Override
public String toString() {
return "GenericInfoAttr{" + Arrays.toString(genericTypes) + ", explicit=" + explicit + '}';
return "GenericInfoAttr{" + genericTypes + ", explicit=" + explicit + '}';
}
}
......@@ -8,7 +8,7 @@ import jadx.core.utils.Utils;
public class IndexInsnNode extends InsnNode {
private final Object index;
private Object index;
public IndexInsnNode(InsnType type, Object index, int argCount) {
super(type, argCount);
......@@ -19,6 +19,10 @@ public class IndexInsnNode extends InsnNode {
return index;
}
public void updateIndex(Object index) {
this.index = index;
}
@Override
public IndexInsnNode copy() {
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
......
......@@ -39,7 +39,7 @@ public class InsnDecoder {
InsnNode insn;
try {
rawInsn.decode();
insn = decode(rawInsn, offset);
insn = decode(rawInsn);
insn.setOffset(offset);
} catch (Exception e) {
LOG.error("Failed to decode insn: " + rawInsn + ", method: " + method, e);
......@@ -51,7 +51,7 @@ public class InsnDecoder {
}
@NotNull
private InsnNode decode(InsnData insn, int offset) throws DecodeException {
private InsnNode decode(InsnData insn) throws DecodeException {
switch (insn.getOpcode()) {
case NOP:
return new InsnNode(InsnType.NOP, 0);
......@@ -402,28 +402,28 @@ public class InsnDecoder {
return arrayPut(insn, ArgType.UNKNOWN_OBJECT);
case INVOKE_STATIC:
return invoke(insn, offset, InvokeType.STATIC, false);
return invoke(insn, InvokeType.STATIC, false);
case INVOKE_STATIC_RANGE:
return invoke(insn, offset, InvokeType.STATIC, true);
return invoke(insn, InvokeType.STATIC, true);
case INVOKE_DIRECT:
return invoke(insn, offset, InvokeType.DIRECT, false);
return invoke(insn, InvokeType.DIRECT, false);
case INVOKE_INTERFACE:
return invoke(insn, offset, InvokeType.INTERFACE, false);
return invoke(insn, InvokeType.INTERFACE, false);
case INVOKE_SUPER:
return invoke(insn, offset, InvokeType.SUPER, false);
return invoke(insn, InvokeType.SUPER, false);
case INVOKE_VIRTUAL:
return invoke(insn, offset, InvokeType.VIRTUAL, false);
return invoke(insn, InvokeType.VIRTUAL, false);
case INVOKE_DIRECT_RANGE:
return invoke(insn, offset, InvokeType.DIRECT, true);
return invoke(insn, InvokeType.DIRECT, true);
case INVOKE_INTERFACE_RANGE:
return invoke(insn, offset, InvokeType.INTERFACE, true);
return invoke(insn, InvokeType.INTERFACE, true);
case INVOKE_SUPER_RANGE:
return invoke(insn, offset, InvokeType.SUPER, true);
return invoke(insn, InvokeType.SUPER, true);
case INVOKE_VIRTUAL_RANGE:
return invoke(insn, offset, InvokeType.VIRTUAL, true);
return invoke(insn, InvokeType.VIRTUAL, true);
case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType());
......@@ -520,7 +520,7 @@ public class InsnDecoder {
return inode;
}
private InsnNode invoke(InsnData insn, int offset, InvokeType type, boolean isRange) {
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
MethodInfo mth = MethodInfo.fromData(root, insn.getIndexAsMethod());
return new InvokeNode(mth, insn, type, isRange);
}
......
......@@ -13,6 +13,7 @@ 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.InsnNode;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -108,15 +109,11 @@ public final class PhiInsn extends InsnNode {
if (argIndex == -1) {
return false;
}
BlockNode pred = getBlockByArgIndex(argIndex);
if (pred == null) {
throw new JadxRuntimeException("Unknown predecessor block by arg " + from + " in PHI: " + this);
}
removeArg(argIndex);
((RegisterArg) to).getSVar().addUsedInPhi(this);
super.setArg(argIndex, to);
RegisterArg reg = (RegisterArg) to;
bindArg(reg, pred);
reg.getSVar().addUsedInPhi(this);
InsnRemover.unbindArgUsage(null, from);
((RegisterArg) from).getSVar().updateUsedInPhiList();
return true;
}
......
......@@ -7,6 +7,7 @@ import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
......@@ -32,6 +33,7 @@ public abstract class ArgType {
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 EXCEPTION = objectNoCache(Consts.CLASS_EXCEPTION);
public static final ArgType OBJECT_ARRAY = array(OBJECT);
public static final ArgType WILDCARD = wildcard();
......@@ -86,6 +88,8 @@ public abstract class ArgType {
return CLASS;
case Consts.CLASS_THROWABLE:
return THROWABLE;
case Consts.CLASS_EXCEPTION:
return EXCEPTION;
default:
return new ObjectType(cleanObjectName);
}
......@@ -95,6 +99,14 @@ public abstract class ArgType {
return new GenericType(type);
}
public static ArgType genericType(String type, ArgType extendType) {
return new GenericType(type, extendType);
}
public static ArgType genericType(String type, List<ArgType> extendTypes) {
return new GenericType(type, extendTypes);
}
public static ArgType wildcard() {
return new WildcardType(OBJECT, WildcardBound.UNBOUND);
}
......@@ -103,16 +115,28 @@ public abstract class ArgType {
return new WildcardType(obj, bound);
}
public static ArgType generic(ArgType obj, ArgType... generics) {
public static ArgType generic(ArgType obj, List<ArgType> generics) {
if (!obj.isObject()) {
throw new IllegalArgumentException("Expected Object as ArgType, got: " + obj);
}
return new GenericObject(obj.getObject(), generics);
}
public static ArgType generic(ArgType obj, ArgType... generics) {
return generic(obj, Arrays.asList(generics));
}
public static ArgType generic(String obj, List<ArgType> generics) {
return new GenericObject(Utils.cleanObjectName(obj), generics);
}
public static ArgType generic(String obj, ArgType generic) {
return generic(obj, Collections.singletonList(generic));
}
@TestOnly
public static ArgType generic(String obj, ArgType... generics) {
String cleanObjectName = Utils.cleanObjectName(obj);
return new GenericObject(cleanObjectName, generics);
return generic(obj, Arrays.asList(generics));
}
public static ArgType outerGeneric(ArgType genericOuterType, ArgType innerType) {
......@@ -219,7 +243,16 @@ public abstract class ArgType {
private List<ArgType> extendTypes;
public GenericType(String obj) {
this(obj, Collections.emptyList());
}
public GenericType(String obj, ArgType extendType) {
this(obj, Collections.singletonList(extendType));
}
public GenericType(String obj, List<ArgType> extendTypes) {
super(obj);
this.extendTypes = extendTypes;
}
@Override
......@@ -236,6 +269,21 @@ public abstract class ArgType {
public void setExtendTypes(List<ArgType> extendTypes) {
this.extendTypes = extendTypes;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& extendTypes.equals(((GenericType) obj).extendTypes);
}
@Override
public String toString() {
List<ArgType> extTypes = this.extendTypes;
if (extTypes.isEmpty()) {
return objName;
}
return objName + " extends " + Utils.listToString(extTypes, " & ");
}
}
public enum WildcardBound {
......@@ -274,6 +322,11 @@ public abstract class ArgType {
this.bound = Objects.requireNonNull(bound);
}
@Override
public boolean isWildcard() {
return true;
}
@Override
public boolean isGeneric() {
return true;
......@@ -306,16 +359,16 @@ public abstract class ArgType {
}
private static class GenericObject extends ObjectType {
private final ArgType[] generics;
private final List<ArgType> generics;
public GenericObject(String obj, ArgType[] generics) {
public GenericObject(String obj, List<ArgType> generics) {
super(obj);
this.generics = Objects.requireNonNull(generics);
this.hash = calcHash();
}
private int calcHash() {
return objName.hashCode() + 31 * Arrays.hashCode(generics);
return objName.hashCode() + 31 * generics.hashCode();
}
@Override
......@@ -324,19 +377,19 @@ public abstract class ArgType {
}
@Override
public ArgType[] getGenericTypes() {
public List<ArgType> getGenericTypes() {
return generics;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Arrays.equals(generics, ((GenericObject) obj).generics);
&& Objects.equals(generics, ((GenericObject) obj).generics);
}
@Override
public String toString() {
return super.toString() + '<' + Utils.arrayToStr(generics) + '>';
return super.toString() + '<' + Utils.listToString(generics) + '>';
}
}
......@@ -361,7 +414,7 @@ public abstract class ArgType {
}
@Override
public ArgType[] getGenericTypes() {
public List<ArgType> getGenericTypes() {
return innerType.getGenericTypes();
}
......@@ -496,9 +549,9 @@ public abstract class ArgType {
@Override
public String toString() {
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
return "??";
} else {
return "?[" + Utils.arrayToStr(possibleTypes) + ']';
return "??[" + Utils.arrayToStr(possibleTypes) + ']';
}
}
}
......@@ -531,7 +584,7 @@ public abstract class ArgType {
return false;
}
public ArgType[] getGenericTypes() {
public List<ArgType> getGenericTypes() {
return null;
}
......@@ -550,6 +603,10 @@ public abstract class ArgType {
return null;
}
public boolean isWildcard() {
return false;
}
public ArgType getOuterType() {
return null;
}
......@@ -726,7 +783,7 @@ public abstract class ArgType {
return wildcardType.containsTypeVariable();
}
if (isGeneric()) {
ArgType[] genericTypes = getGenericTypes();
List<ArgType> genericTypes = getGenericTypes();
if (genericTypes != null) {
for (ArgType genericType : genericTypes) {
if (genericType.containsTypeVariable()) {
......@@ -754,21 +811,27 @@ public abstract class ArgType {
if (r != null) {
return r;
}
ArgType wildcardType = getWildcardType();
if (wildcardType != null) {
return wildcardType.visitTypes(visitor);
}
if (isArray()) {
ArgType arrayElement = getArrayElement();
if (arrayElement != null) {
return arrayElement.visitTypes(visitor);
}
}
ArgType wildcardType = getWildcardType();
if (wildcardType != null) {
R res = wildcardType.visitTypes(visitor);
if (res != null) {
return res;
}
}
if (isGeneric()) {
ArgType[] genericTypes = getGenericTypes();
List<ArgType> genericTypes = getGenericTypes();
if (genericTypes != null) {
for (ArgType genericType : genericTypes) {
return genericType.visitTypes(visitor);
R res = genericType.visitTypes(visitor);
if (res != null) {
return res;
}
}
}
}
......
......@@ -7,21 +7,22 @@ import java.util.List;
public class CodeVar {
private String name;
private ArgType type; // before type inference can be null and set only for immutable types
private List<SSAVar> ssaVars = new ArrayList<>(3);
private List<SSAVar> ssaVars = Collections.emptyList();
private boolean isFinal;
private boolean isThis;
private boolean isDeclared;
public static CodeVar fromMthArg(RegisterArg mthArg) {
public static CodeVar fromMthArg(RegisterArg mthArg, boolean linkRegister) {
CodeVar var = new CodeVar();
var.setType(mthArg.getInitType());
var.setName(mthArg.getName());
var.setThis(mthArg.isThis());
var.setDeclared(true);
var.setThis(mthArg.isThis());
SSAVar ssaVar = new SSAVar(mthArg.getRegNum(), 0, mthArg);
ssaVar.setCodeVar(var);
var.setSsaVars(Collections.singletonList(ssaVar));
if (linkRegister) {
var.setSsaVars(Collections.singletonList(new SSAVar(mthArg.getRegNum(), 0, mthArg)));
}
return var;
}
......@@ -46,6 +47,9 @@ public class CodeVar {
}
public void addSsaVar(SSAVar ssaVar) {
if (ssaVars.isEmpty()) {
ssaVars = new ArrayList<>(3);
}
if (!ssaVars.contains(ssaVar)) {
ssaVars.add(ssaVar);
}
......
......@@ -32,12 +32,8 @@ public class RegisterArg extends InsnArg implements Named {
return true;
}
@Override
public void setType(ArgType newType) {
if (sVar == null) {
throw new JadxRuntimeException("Can't change type for register without SSA variable: " + this);
}
sVar.setType(newType);
public ArgType getInitType() {
return type;
}
@Override
......@@ -48,27 +44,39 @@ public class RegisterArg extends InsnArg implements Named {
return ArgType.UNKNOWN;
}
public ArgType getInitType() {
return type;
@Override
public void setType(ArgType newType) {
if (sVar == null) {
throw new JadxRuntimeException("Can't change type for register without SSA variable: " + this);
}
sVar.setType(newType);
}
public void updateImmutableType(ArgType type) {
if (sVar == null) {
throw new JadxRuntimeException("Unknown SSA variable to update immutable type: " + this);
}
sVar.forceSetType(type);
sVar.getAssign().add(AFlag.IMMUTABLE_TYPE);
}
@Nullable
public ArgType getImmutableType() {
if (contains(AFlag.IMMUTABLE_TYPE)) {
return type;
}
if (sVar != null) {
return sVar.getImmutableType();
}
if (contains(AFlag.IMMUTABLE_TYPE)) {
return type;
}
return null;
}
@Override
public boolean isTypeImmutable() {
if (contains(AFlag.IMMUTABLE_TYPE)) {
return true;
if (sVar != null) {
return sVar.isTypeImmutable();
}
return sVar != null && sVar.isTypeImmutable();
return contains(AFlag.IMMUTABLE_TYPE);
}
public SSAVar getSVar() {
......@@ -79,14 +87,6 @@ public class RegisterArg extends InsnArg implements Named {
this.sVar = sVar;
}
@Override
public void add(AFlag flag) {
if (flag == AFlag.IMMUTABLE_TYPE && !type.isTypeKnown()) {
throw new JadxRuntimeException("Can't mark unknown type as immutable, type: " + type + ", reg: " + this);
}
super.add(flag);
}
@Override
public String getName() {
if (isSuper()) {
......
......@@ -28,7 +28,7 @@ public class SSAVar {
private final List<RegisterArg> useList = new ArrayList<>(2);
private List<PhiInsn> usedInPhi = null;
private TypeInfo typeInfo = new TypeInfo();
private final TypeInfo typeInfo = new TypeInfo();
@Nullable("Set in InitCodeVariables pass")
private CodeVar codeVar;
......@@ -69,18 +69,13 @@ public class SSAVar {
@Nullable
public ArgType getImmutableType() {
if (assign.contains(AFlag.IMMUTABLE_TYPE)) {
return assign.getInitType();
}
for (RegisterArg useArg : useList) {
if (useArg.contains(AFlag.IMMUTABLE_TYPE)) {
return useArg.getInitType();
}
return assign.getType();
}
return null;
}
public boolean isTypeImmutable() {
return getImmutableType() != null;
return assign.contains(AFlag.IMMUTABLE_TYPE);
}
public void setType(ArgType type) {
......@@ -88,7 +83,14 @@ public class SSAVar {
if (imType != null && !imType.equals(type)) {
throw new JadxRuntimeException("Can't change immutable type " + imType + " to " + type + " for " + this);
}
updateType(type);
}
public void forceSetType(ArgType type) {
updateType(type);
}
private void updateType(ArgType type) {
typeInfo.setType(type);
if (codeVar != null) {
codeVar.setType(type);
......@@ -194,10 +196,17 @@ public class SSAVar {
public void setCodeVar(@NotNull CodeVar codeVar) {
this.codeVar = codeVar;
codeVar.addSsaVar(this);
ArgType imType = getImmutableType();
if (imType != null) {
codeVar.setType(imType);
}
}
public void resetTypeAndCodeVar() {
this.typeInfo.reset();
if (!isTypeImmutable()) {
updateType(ArgType.UNKNOWN);
}
this.typeInfo.getBounds().clear();
this.codeVar = null;
}
......
......@@ -56,7 +56,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
private AccessInfo accessFlags;
private ArgType superClass;
private List<ArgType> interfaces;
private List<GenericTypeParameter> generics = Collections.emptyList();
private List<ArgType> generics = Collections.emptyList();
private final List<MethodNode> methods;
private final List<FieldNode> fields;
......@@ -224,7 +224,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
if (sp != null) {
ArgType gType = sp.consumeType();
if (gType != null) {
field.setType(gType);
field.setType(root.getTypeUtils().expandTypeVariables(this, gType));
}
}
} catch (Exception e) {
......@@ -337,10 +337,18 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return interfaces;
}
public List<GenericTypeParameter> getGenericTypeParameters() {
public List<ArgType> getGenericTypeParameters() {
return generics;
}
public ArgType getType() {
ArgType clsType = clsInfo.getType();
if (Utils.notEmpty(generics)) {
return ArgType.generic(clsType, generics);
}
return clsType;
}
public List<MethodNode> getMethods() {
return methods;
}
......
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();
}
}
......@@ -16,7 +16,7 @@ public interface IMethodDetails extends IAttribute {
List<ArgType> getArgTypes();
List<GenericTypeParameter> getTypeParameters();
List<ArgType> getTypeParameters();
List<ArgType> getThrows();
......
......@@ -32,6 +32,7 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.Utils;
......@@ -59,7 +60,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
// additional info available after load, keep on unload
private ArgType retType;
private List<ArgType> argTypes;
private List<GenericTypeParameter> typeParameters;
private List<ArgType> typeParameters;
// decompilation data, reset on unload
private RegisterArg thisArg;
......@@ -172,41 +173,41 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
public void initMethodTypes() {
List<ArgType> types = parseSignature();
if (types == null) {
if (!parseSignature()) {
this.retType = mthInfo.getReturnType();
this.argTypes = mthInfo.getArgumentsTypes();
this.typeParameters = Collections.emptyList();
} else {
this.argTypes = Collections.unmodifiableList(types);
}
}
@Nullable
private List<ArgType> parseSignature() {
private boolean parseSignature() {
SignatureParser sp = SignatureParser.fromNode(this);
if (sp == null) {
return null;
return false;
}
try {
this.typeParameters = sp.consumeGenericTypeParameters();
List<ArgType> argsTypes = sp.consumeMethodArgs();
this.retType = sp.consumeType();
List<ArgType> parsedArgTypes = sp.consumeMethodArgs();
ArgType parsedRetType = sp.consumeType();
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
if (argsTypes.size() != mthArgs.size()) {
if (argsTypes.isEmpty()) {
return null;
if (parsedArgTypes.size() != mthArgs.size()) {
if (parsedArgTypes.isEmpty()) {
return false;
}
if (!tryFixArgsCounts(argsTypes, mthArgs)) {
addComment("Incorrect method signature, types: " + Utils.listToString(argsTypes));
return null;
if (!tryFixArgsCounts(parsedArgTypes, mthArgs)) {
addComment("Incorrect method signature, types: " + Utils.listToString(parsedArgTypes));
return false;
}
}
return argsTypes;
TypeUtils typeUtils = root().getTypeUtils();
this.retType = typeUtils.expandTypeVariables(this, parsedRetType);
this.argTypes = Collections.unmodifiableList(Utils.collectionMap(parsedArgTypes,
t -> typeUtils.expandTypeVariables(this, t)));
return true;
} catch (Exception e) {
addWarnComment("Failed to parse method signature: " + sp.getSignature(), e);
return null;
return false;
}
}
......@@ -239,10 +240,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
pos -= arg.getRegCount();
}
}
TypeUtils typeUtils = root().getTypeUtils();
if (accFlags.isStatic()) {
thisArg = null;
} else {
RegisterArg arg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
ArgType thisClsType = typeUtils.expandTypeVariables(this, parentClass.getType());
RegisterArg arg = InsnArg.reg(pos - 1, thisClsType);
arg.add(AFlag.THIS);
arg.add(AFlag.IMMUTABLE_TYPE);
thisArg = arg;
......@@ -253,7 +256,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
argsList = new ArrayList<>(args.size());
for (ArgType argType : args) {
RegisterArg regArg = InsnArg.reg(pos, argType);
ArgType expandedType = typeUtils.expandTypeVariables(this, argType);
RegisterArg regArg = InsnArg.reg(pos, expandedType);
regArg.add(AFlag.METHOD_ARGUMENT);
regArg.add(AFlag.IMMUTABLE_TYPE);
argsList.add(regArg);
......@@ -323,7 +327,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
}
@Override
public List<GenericTypeParameter> getTypeParameters() {
public List<ArgType> getTypeParameters() {
return typeParameters;
}
......
......@@ -153,11 +153,10 @@ public class RootNode {
ClspGraph newClsp = new ClspGraph(this);
newClsp.load();
newClsp.addApp(classes);
this.clsp = newClsp;
}
} catch (Exception e) {
throw new JadxRuntimeException("Error loading classpath", e);
throw new JadxRuntimeException("Error loading jadx class set", e);
}
}
......
......@@ -14,13 +14,12 @@ import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.GenericTypeParameter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SignatureParser {
private static final Logger LOG = LoggerFactory.getLogger(SignatureParser.class);
private static final char STOP_CHAR = 0;
private final String sign;
......@@ -201,10 +200,10 @@ public class SignatureParser {
if (!innerType) {
obj += ';';
}
ArgType[] genArr = consumeGenericArgs();
List<ArgType> typeVars = consumeGenericArgs();
consume('>');
ArgType genericType = ArgType.generic(obj, genArr);
ArgType genericType = ArgType.generic(obj, typeVars);
if (!lookAhead('.')) {
consume(';');
return genericType;
......@@ -229,7 +228,7 @@ public class SignatureParser {
return ArgType.outerGeneric(genericType, inner);
}
private ArgType[] consumeGenericArgs() {
private List<ArgType> consumeGenericArgs() {
List<ArgType> list = new LinkedList<>();
ArgType type;
do {
......@@ -249,7 +248,7 @@ public class SignatureParser {
list.add(type);
}
} while (type != null && !lookAhead('>'));
return list.toArray(new ArgType[0]);
return list;
}
/**
......@@ -257,11 +256,11 @@ public class SignatureParser {
* <p/>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
*/
public List<GenericTypeParameter> consumeGenericTypeParameters() {
public List<ArgType> consumeGenericTypeParameters() {
if (!lookAhead('<')) {
return Collections.emptyList();
}
List<GenericTypeParameter> list = new ArrayList<>();
List<ArgType> list = new ArrayList<>();
consume('<');
while (true) {
if (lookAhead('>') || next() == STOP_CHAR) {
......@@ -275,7 +274,7 @@ public class SignatureParser {
consume(':');
tryConsume(':');
List<ArgType> types = consumeExtendsTypesList();
list.add(new GenericTypeParameter(ArgType.genericType(id), types));
list.add(ArgType.genericType(id, types));
}
consume('>');
return list;
......
package jadx.core.dex.nodes.utils;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
......@@ -16,10 +17,14 @@ 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.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.Utils.isEmpty;
import static jadx.core.utils.Utils.notEmpty;
public class TypeUtils {
private final RootNode root;
......@@ -28,7 +33,7 @@ public class TypeUtils {
this.root = rootNode;
}
public List<GenericTypeParameter> getClassGenerics(ArgType type) {
public List<ArgType> getClassGenerics(ArgType type) {
ClassNode classNode = root.resolveClass(type);
if (classNode != null) {
return classNode.getGenericTypeParameters();
......@@ -37,10 +42,50 @@ public class TypeUtils {
if (clsDetails == null || clsDetails.getTypeParameters().isEmpty()) {
return Collections.emptyList();
}
List<GenericTypeParameter> generics = clsDetails.getTypeParameters();
List<ArgType> generics = clsDetails.getTypeParameters();
return generics == null ? Collections.emptyList() : generics;
}
public ArgType expandTypeVariables(ClassNode cls, ArgType type) {
if (type.containsTypeVariable()) {
expandTypeVar(type, cls.getGenericTypeParameters());
}
return type;
}
public ArgType expandTypeVariables(MethodNode mth, ArgType type) {
if (type.containsTypeVariable()) {
expandTypeVar(type, getKnownTypeVarsAtMethod(mth));
}
return type;
}
private void expandTypeVar(ArgType type, Collection<ArgType> typeVars) {
boolean allExtendsEmpty = true;
for (ArgType argType : typeVars) {
if (notEmpty(argType.getExtendTypes())) {
allExtendsEmpty = false;
break;
}
}
if (allExtendsEmpty) {
return;
}
type.visitTypes(t -> {
if (t.isGenericType()) {
String typeVarName = t.getObject();
for (ArgType typeVar : typeVars) {
if (typeVar.getObject().equals(typeVarName)) {
t.setExtendTypes(typeVar.getExtendTypes());
return null;
}
}
throw new JadxRuntimeException("Can't find type var definition: " + typeVarName);
}
return null;
});
}
public Set<ArgType> getKnownTypeVarsAtMethod(MethodNode mth) {
MethodTypeVarsAttr typeVarsAttr = mth.get(AType.METHOD_TYPE_VARS);
if (typeVarsAttr != null) {
......@@ -52,27 +97,14 @@ public class TypeUtils {
}
private static Set<ArgType> collectKnownTypeVarsAtMethod(MethodNode mth) {
Set<ArgType> typeVars = new HashSet<>();
ClassNode declCls = mth.getParentClass();
addTypeVarsFromCls(typeVars, declCls);
declCls.visitParentClasses(parent -> addTypeVarsFromCls(typeVars, parent));
Set<ArgType> typeVars = new HashSet<>(declCls.getGenericTypeParameters());
declCls.visitParentClasses(parent -> typeVars.addAll(parent.getGenericTypeParameters()));
for (GenericTypeParameter typeParameter : mth.getTypeParameters()) {
typeVars.add(typeParameter.getTypeVariable());
}
typeVars.addAll(mth.getTypeParameters());
return typeVars.isEmpty() ? Collections.emptySet() : typeVars;
}
private static void addTypeVarsFromCls(Set<ArgType> typeVars, ClassNode parentCls) {
List<GenericTypeParameter> typeParameters = parentCls.getGenericTypeParameters();
if (typeParameters.isEmpty()) {
return;
}
for (GenericTypeParameter typeParameter : typeParameters) {
typeVars.add(typeParameter.getTypeVariable());
}
}
/**
* Replace generic types in {@code typeWithGeneric} using instance types
* <br>
......@@ -100,23 +132,23 @@ public class TypeUtils {
return Collections.emptyMap();
}
List<GenericTypeParameter> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
List<ArgType> typeParameters = root.getTypeUtils().getClassGenerics(clsType);
if (typeParameters.isEmpty()) {
return Collections.emptyMap();
}
ArgType[] actualTypes = clsType.getGenericTypes();
if (actualTypes == null) {
List<ArgType> actualTypes = clsType.getGenericTypes();
if (isEmpty(actualTypes)) {
return Collections.emptyMap();
}
int genericParamsCount = actualTypes.length;
int genericParamsCount = actualTypes.size();
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);
ArgType actualType = actualTypes.get(i);
ArgType typeVar = typeParameters.get(i);
replaceMap.put(typeVar, actualType);
}
return replaceMap;
}
......@@ -169,19 +201,13 @@ public class TypeUtils {
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);
List<ArgType> genericTypes = replaceType.getGenericTypes();
if (replaceType.isGeneric() && notEmpty(genericTypes)) {
List<ArgType> newTypes = Utils.collectionMap(genericTypes, t -> {
ArgType type = replaceTypeVariablesUsingMap(t, replaceMap);
return type == null ? t : type;
});
return ArgType.generic(replaceType, newTypes);
}
return null;
}
......
......@@ -139,7 +139,7 @@ public class ModVisitor extends AbstractVisitor {
break;
case CHECK_CAST:
removeRedundantCast(mth, block, i, (IndexInsnNode) insn);
removeCheckCast(mth, block, i, (IndexInsnNode) insn);
break;
case CAST:
......@@ -304,15 +304,18 @@ public class ModVisitor extends AbstractVisitor {
return false;
}
private static void removeRedundantCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
InsnArg castArg = insn.getArg(0);
ArgType castType = (ArgType) insn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)
|| isCastDuplicate(insn)) {
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setResult(insn.getResult());
insnNode.addArg(castArg);
replaceInsn(mth, block, i, insnNode);
RegisterArg result = insn.getResult();
result.setType(castArg.getType());
InsnNode move = new InsnNode(InsnType.MOVE, 1);
move.setResult(result);
move.addArg(castArg);
replaceInsn(mth, block, i, move);
}
}
......
......@@ -71,6 +71,9 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
}
private static void applyDebugInfo(MethodNode mth) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Apply debug info for method: {}", mth);
}
mth.getSVars().forEach(ssaVar -> collectVarDebugInfo(mth, ssaVar));
fixLinesForReturn(mth);
......@@ -117,10 +120,10 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
int startAddr = localVar.getStartOffset();
int endAddr = localVar.getEndOffset();
if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) {
if (Consts.DEBUG) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar);
}
ArgType type = DebugInfoAttachVisitor.getVarType(localVar);
ArgType type = DebugInfoAttachVisitor.getVarType(mth, localVar);
applyDebugInfo(mth, ssaVar, type, localVar.getName());
break;
}
......@@ -145,7 +148,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(mth, ssaVar, type);
if (result == TypeUpdateResult.REJECT) {
if (Consts.DEBUG) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
}
} else {
......
......@@ -79,7 +79,7 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
int start = var.getStartOffset();
int end = var.getEndOffset();
ArgType type = getVarType(var);
ArgType type = getVarType(mth, var);
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(type, var.getName());
if (start < 0) {
// attach to method arguments
......@@ -115,7 +115,7 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
}
}
public static ArgType getVarType(ILocalVar var) {
public static ArgType getVarType(MethodNode mth, ILocalVar var) {
ArgType type = ArgType.parse(var.getType());
String sign = var.getSignature();
if (sign == null) {
......@@ -123,8 +123,9 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
}
try {
ArgType gType = new SignatureParser(sign).consumeType();
if (checkSignature(type, gType)) {
return gType;
ArgType expandedType = mth.root().getTypeUtils().expandTypeVariables(mth, gType);
if (checkSignature(type, expandedType)) {
return expandedType;
}
} catch (Exception e) {
LOG.error("Can't parse signature for local variable: {}", sign, e);
......
......@@ -5,7 +5,6 @@ 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 MutableMethodDetails implements IMethodDetails {
......@@ -13,7 +12,7 @@ public class MutableMethodDetails implements IMethodDetails {
private final MethodInfo mthInfo;
private ArgType retType;
private List<ArgType> argTypes;
private List<GenericTypeParameter> typeParams;
private List<ArgType> typeParams;
private List<ArgType> throwTypes;
private boolean varArg;
......@@ -42,7 +41,7 @@ public class MutableMethodDetails implements IMethodDetails {
}
@Override
public List<GenericTypeParameter> getTypeParameters() {
public List<ArgType> getTypeParameters() {
return typeParams;
}
......@@ -64,7 +63,7 @@ public class MutableMethodDetails implements IMethodDetails {
this.argTypes = argTypes;
}
public void setTypeParams(List<GenericTypeParameter> typeParams) {
public void setTypeParams(List<ArgType> typeParams) {
this.typeParams = typeParams;
}
......
......@@ -327,11 +327,11 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
ArgType iterableType = iterableArg.getType();
ArgType varType = iterVar.getType();
if (iterableType.isGeneric()) {
ArgType[] genericTypes = iterableType.getGenericTypes();
if (genericTypes == null || genericTypes.length != 1) {
List<ArgType> genericTypes = iterableType.getGenericTypes();
if (genericTypes == null || genericTypes.size() != 1) {
return false;
}
ArgType gType = genericTypes[0];
ArgType gType = genericTypes.get(0);
if (gType.equals(varType)) {
return true;
}
......
......@@ -413,6 +413,7 @@ public class SSATransform extends AbstractVisitor {
return;
}
arg.add(AFlag.THIS);
arg.add(AFlag.IMMUTABLE_TYPE);
// mark all moved 'this'
InsnNode parentInsn = arg.getParentInsn();
if (parentInsn != null
......
package jadx.core.dex.visitors.typeinference;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.RootNode;
/**
* Allow to ignore down casts (return arg type instead cast type)
* Such casts will be removed later.
*/
public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
private final RootNode root;
private final IndexInsnNode insn;
public TypeBoundCheckCastAssign(RootNode root, IndexInsnNode insn) {
this.root = root;
this.insn = insn;
}
@Override
public BoundEnum getBound() {
return BoundEnum.ASSIGN;
}
@Override
public ArgType getType(TypeUpdateInfo updateInfo) {
return getReturnType(updateInfo.getType(insn.getArg(0)));
}
@Override
public ArgType getType() {
return getReturnType(insn.getArg(0).getType());
}
private ArgType getReturnType(ArgType argType) {
ArgType castType = (ArgType) insn.getIndex();
TypeCompareEnum result = root.getTypeCompare().compareTypes(argType, castType);
return result.isNarrow() ? argType : castType;
}
@Override
public @Nullable RegisterArg getArg() {
return insn.getResult();
}
@Override
public String toString() {
return "CHECK_CAST_ASSIGN{(" + insn.getIndex() + ") " + insn.getArg(0).getType() + "}";
}
}
......@@ -38,7 +38,7 @@ public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
private ArgType getReturnType(ArgType instanceType) {
ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, genericReturnType);
if (resultGeneric != null) {
if (resultGeneric != null && !resultGeneric.isWildcard()) {
return resultGeneric;
}
return invokeNode.getCallMth().getReturnType();
......
package jadx.core.dex.visitors.typeinference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
......@@ -20,6 +22,7 @@ import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GEN
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.UNKNOWN;
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.WIDER;
import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.WIDER_BY_GENERIC;
import static jadx.core.utils.Utils.isEmpty;
public class TypeCompare {
private static final Logger LOG = LoggerFactory.getLogger(TypeCompare.class);
......@@ -179,9 +182,9 @@ public class TypeCompare {
// both wildcards
return compareWildcardTypes(first, second);
}
ArgType[] firstGenericTypes = first.getGenericTypes();
ArgType[] secondGenericTypes = second.getGenericTypes();
if (firstGenericTypes == null || secondGenericTypes == null) {
List<ArgType> firstGenericTypes = first.getGenericTypes();
List<ArgType> secondGenericTypes = second.getGenericTypes();
if (isEmpty(firstGenericTypes) || isEmpty(secondGenericTypes)) {
// check outer types
ArgType firstOuterType = first.getOuterType();
ArgType secondOuterType = second.getOuterType();
......@@ -190,10 +193,10 @@ public class TypeCompare {
}
} else {
// compare generics arrays
int len = firstGenericTypes.length;
if (len == secondGenericTypes.length) {
int len = firstGenericTypes.size();
if (len == secondGenericTypes.size()) {
for (int i = 0; i < len; i++) {
TypeCompareEnum res = compareTypes(firstGenericTypes[i], secondGenericTypes[i]);
TypeCompareEnum res = compareTypes(firstGenericTypes.get(i), secondGenericTypes.get(i));
if (res != EQUAL) {
return res;
}
......@@ -234,25 +237,58 @@ public class TypeCompare {
}
private TypeCompareEnum compareGenericTypeWithObject(ArgType genericType, ArgType objType) {
if (objType.isGenericType()) {
return compareTypeVariables(genericType, objType);
}
List<ArgType> extendTypes = genericType.getExtendTypes();
if (extendTypes == null || extendTypes.isEmpty()) {
if (objType.equals(ArgType.OBJECT)) {
return NARROW_BY_GENERIC;
if (extendTypes.isEmpty()) {
return NARROW;
}
if (extendTypes.contains(objType) || objType.equals(ArgType.OBJECT)) {
return NARROW;
}
for (ArgType extendType : extendTypes) {
TypeCompareEnum res = compareObjects(extendType, objType);
if (!res.isNarrow()) {
return res;
}
} else {
if (extendTypes.contains(objType) || objType.equals(ArgType.OBJECT)) {
return NARROW_BY_GENERIC;
}
return NARROW;
}
private TypeCompareEnum compareTypeVariables(ArgType first, ArgType second) {
if (first.getObject().equals(second.getObject())) {
List<ArgType> firstExtendTypes = removeObject(first.getExtendTypes());
List<ArgType> secondExtendTypes = removeObject(second.getExtendTypes());
if (firstExtendTypes.equals(secondExtendTypes)) {
return EQUAL;
}
for (ArgType extendType : extendTypes) {
if (!ArgType.isInstanceOf(root, extendType, objType)) {
return CONFLICT;
}
int firstExtSize = firstExtendTypes.size();
int secondExtSize = secondExtendTypes.size();
if (firstExtSize == 0) {
return WIDER;
}
if (secondExtSize == 0) {
return NARROW;
}
if (firstExtSize == 1 && secondExtSize == 1) {
return compareTypes(firstExtendTypes.get(0), secondExtendTypes.get(0));
}
}
return CONFLICT;
}
private List<ArgType> removeObject(List<ArgType> extendTypes) {
if (extendTypes.contains(ArgType.OBJECT)) {
if (extendTypes.size() == 1) {
return Collections.emptyList();
}
return NARROW_BY_GENERIC;
List<ArgType> result = new ArrayList<>(extendTypes);
result.remove(ArgType.OBJECT);
return result;
}
// TODO: fill extendTypes
// return CONFLICT;
return NARROW_BY_GENERIC;
return extendTypes;
}
public Comparator<ArgType> getComparator() {
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.typeinference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
......@@ -17,6 +18,7 @@ import jadx.core.Consts;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.IndexInsnNode;
......@@ -72,7 +74,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
this.root = root;
this.typeUpdate = root.getTypeUpdate();
this.resolvers = Arrays.asList(
this::initTypeBounds,
this::runTypePropagation,
this::tryInsertCasts,
this::tryDeduceTypes,
this::trySplitConstInsns,
this::tryToFixIncompatiblePrimitives,
......@@ -89,6 +93,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Start type inference in method: {}", mth);
}
assignImmutableTypes(mth);
try {
for (Function<MethodNode, Boolean> resolver : resolvers) {
if (resolver.apply(mth) && checkTypes(mth)) {
......@@ -114,16 +119,24 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
/**
* Guess type from usage and try to set it to current variable
* and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)}
* Collect initial type bounds from assign and usages
*/
private boolean runTypePropagation(MethodNode mth) {
private boolean initTypeBounds(MethodNode mth) {
List<SSAVar> ssaVars = mth.getSVars();
// collect initial type bounds from assign and usages`
ssaVars.forEach(this::attachBounds);
ssaVars.forEach(this::mergePhiBounds);
if (Consts.DEBUG_TYPE_INFERENCE) {
ssaVars.forEach(ssaVar -> LOG.debug("Type bounds for {}: {}", ssaVar.toShortString(), ssaVar.getTypeInfo().getBounds()));
}
return false;
}
// start initial type propagation
/**
* Guess type from usage and try to set it to current variable
* and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)}
*/
private boolean runTypePropagation(MethodNode mth) {
List<SSAVar> ssaVars = mth.getSVars();
ssaVars.forEach(var -> setImmutableType(mth, var));
ssaVars.forEach(var -> setBestType(mth, var));
return true;
......@@ -151,7 +164,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
try {
ArgType immutableType = ssaVar.getImmutableType();
if (immutableType != null) {
applyImmutableType(mth, ssaVar, immutableType);
TypeUpdateResult result = typeUpdate.applyWithWiderIgnSame(mth, ssaVar, immutableType);
if (Consts.DEBUG_TYPE_INFERENCE && result == TypeUpdateResult.REJECT) {
LOG.info("Reject initial immutable type {} for {}", immutableType, ssaVar);
}
}
} catch (JadxOverflowException e) {
throw e;
......@@ -171,13 +187,6 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
}
private void applyImmutableType(MethodNode mth, SSAVar ssaVar, ArgType initType) {
TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, initType);
if (Consts.DEBUG_TYPE_INFERENCE && result == TypeUpdateResult.REJECT) {
LOG.info("Reject initial immutable type {} for {}", initType, ssaVar);
}
}
private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) {
TypeInfo typeInfo = ssaVar.getTypeInfo();
Set<ITypeBound> bounds = typeInfo.getBounds();
......@@ -281,6 +290,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
addBound(typeInfo, makeAssignInvokeBound((InvokeNode) insn));
break;
case CHECK_CAST:
addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn));
break;
default:
ArgType type = insn.getResult().getInitType();
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, type));
......@@ -338,7 +351,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
List<ArgType> types = makePossibleTypesList(type);
List<ArgType> types = makePossibleTypesList(type, var);
if (types.isEmpty()) {
return false;
}
for (ArgType candidateType : types) {
TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType);
if (result == TypeUpdateResult.CHANGED) {
......@@ -348,13 +364,24 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false;
}
private List<ArgType> makePossibleTypesList(ArgType type) {
List<ArgType> list = new ArrayList<>();
private List<ArgType> makePossibleTypesList(ArgType type, @Nullable SSAVar var) {
if (type.isArray()) {
for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement())) {
List<ArgType> list = new ArrayList<>();
for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement(), null)) {
list.add(ArgType.array(arrElemType));
}
return list;
}
if (var != null) {
for (ITypeBound b : var.getTypeInfo().getBounds()) {
ArgType boundType = b.getType();
if (boundType.isObject() || boundType.isArray()) {
// don't add primitive types
return Collections.emptyList();
}
}
}
List<ArgType> list = new ArrayList<>();
for (PrimitiveType possibleType : type.getPossibleTypes()) {
if (possibleType == PrimitiveType.VOID) {
continue;
......@@ -433,13 +460,69 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) {
if (objType.isObject() && objType.containsGeneric()) {
ArgType rawType = ArgType.object(objType.getObject());
ArgType rawType = objType.isGenericType() ? ArgType.OBJECT : ArgType.object(objType.getObject());
TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType);
return result == TypeUpdateResult.CHANGED;
}
return false;
}
@SuppressWarnings("ForLoopReplaceableByWhile")
private boolean tryInsertCasts(MethodNode mth) {
int added = 0;
List<SSAVar> mthSVars = mth.getSVars();
int varsCount = mthSVars.size();
for (int i = 0; i < varsCount; i++) {
SSAVar var = mthSVars.get(i);
ArgType type = var.getTypeInfo().getType();
if (!type.isTypeKnown() && !var.isTypeImmutable()) {
added += tryInsertVarCast(mth, var);
}
}
if (added != 0) {
if (Consts.DEBUG_TYPE_INFERENCE) {
mth.addDebugComment("Additional " + added + " cast instructions added to help type inference");
}
InitCodeVariables.rerun(mth);
initTypeBounds(mth);
return runTypePropagation(mth);
}
return false;
}
private int tryInsertVarCast(MethodNode mth, SSAVar var) {
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
ArgType boundType = bound.getType();
if (boundType.isTypeKnown() && boundType.containsTypeVariable()) {
if (insertAssignCast(mth, var, boundType)) {
return 1;
}
// TODO: check if use casts are needed
return 0;
}
}
return 0;
}
private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) {
RegisterArg assignArg = var.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
if (assignBlock == null) {
return false;
}
RegisterArg newAssignArg = assignArg.duplicateWithNewSSAVar(mth);
assignInsn.setResult(newAssignArg);
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
castInsn.setResult(assignArg.duplicate());
castInsn.addArg(newAssignArg.duplicate());
castInsn.add(AFlag.SOFT_CAST);
castInsn.add(AFlag.SYNTHETIC);
return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn);
}
private boolean trySplitConstInsns(MethodNode mth) {
boolean constSplitted = false;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
......@@ -451,6 +534,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false;
}
InitCodeVariables.rerun(mth);
initTypeBounds(mth);
return runTypePropagation(mth);
}
......@@ -486,15 +570,22 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
private boolean tryInsertAdditionalMove(MethodNode mth) {
int insnsAdded = 0;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
insnsAdded += tryInsertAdditionalInsn(mth, var);
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
if (phiListAttr != null) {
for (PhiInsn phiInsn : phiListAttr.getList()) {
insnsAdded += tryInsertAdditionalInsn(mth, phiInsn);
}
}
}
if (insnsAdded == 0) {
return false;
}
mth.addDebugComment("Additional " + insnsAdded + " move instruction added to help type inference");
if (Consts.DEBUG_TYPE_INFERENCE) {
mth.addDebugComment("Additional " + insnsAdded + " move instructions added to help type inference");
}
InitCodeVariables.rerun(mth);
initTypeBounds(mth);
if (runTypePropagation(mth) && checkTypes(mth)) {
return true;
}
......@@ -505,66 +596,79 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
* Add MOVE instruction before PHI in bound blocks to make 'soft' type link.
* This allows to use different types in blocks merged by PHI.
*/
private int tryInsertAdditionalInsn(MethodNode mth, SSAVar var) {
if (var.getTypeInfo().getType().isTypeKnown()) {
private int tryInsertAdditionalInsn(MethodNode mth, PhiInsn phiInsn) {
ArgType phiType = getCommonTypeForPhiArgs(phiInsn);
if (phiType != null && phiType.isTypeKnown()) {
// all args have same known type => nothing to do here
return 0;
}
List<PhiInsn> usedInPhiList = var.getUsedInPhi();
if (usedInPhiList.isEmpty()) {
// check if instructions can be inserted
if (insertMovesForPhi(mth, phiInsn, false) == 0) {
return 0;
}
InsnNode assignInsn = var.getAssign().getAssignInsn();
if (assignInsn != null) {
InsnType assignType = assignInsn.getType();
if (assignType == InsnType.CONST) {
return 0;
}
if (assignType == InsnType.MOVE && var.getUseCount() == 1) {
return 0;
}
}
for (PhiInsn phiInsn : usedInPhiList) {
if (!insertMoveForPhi(mth, phiInsn, var, false)) {
return 0;
}
}
// check passed => apply
return insertMovesForPhi(mth, phiInsn, true);
}
// all check passed => apply
for (PhiInsn phiInsn : usedInPhiList) {
insertMoveForPhi(mth, phiInsn, var, true);
@Nullable
private ArgType getCommonTypeForPhiArgs(PhiInsn phiInsn) {
ArgType phiArgType = null;
for (InsnArg arg : phiInsn.getArguments()) {
ArgType type = arg.getType();
if (phiArgType == null) {
phiArgType = type;
} else if (!phiArgType.equals(type)) {
return null;
}
}
return usedInPhiList.size();
return phiArgType;
}
private boolean insertMoveForPhi(MethodNode mth, PhiInsn phiInsn, SSAVar var, boolean apply) {
private int insertMovesForPhi(MethodNode mth, PhiInsn phiInsn, boolean apply) {
int argsCount = phiInsn.getArgsCount();
int count = 0;
for (int argIndex = 0; argIndex < argsCount; argIndex++) {
RegisterArg reg = phiInsn.getArg(argIndex);
if (reg.getSVar() == var) {
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;
BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex);
BlockNode blockNode = checkBlockForInsnInsert(startBlock);
if (blockNode == null) {
mth.addDebugComment("Failed to insert an additional move for type inference into block " + startBlock);
return 0;
}
boolean add = true;
SSAVar var = reg.getSVar();
InsnNode assignInsn = var.getAssign().getAssignInsn();
if (assignInsn != null) {
InsnType assignType = assignInsn.getType();
if (assignType == InsnType.CONST
|| (assignType == InsnType.MOVE && var.getUseCount() == 1)) {
add = false;
}
}
if (add) {
count++;
if (apply) {
int regNum = reg.getRegNum();
RegisterArg resultArg = reg.duplicate(regNum, null);
SSAVar newSsaVar = mth.makeNewSVar(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);
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
insertMove(mth, blockNode, phiInsn, reg);
}
return true;
}
}
return false;
return count;
}
private void insertMove(MethodNode mth, BlockNode blockNode, PhiInsn phiInsn, RegisterArg reg) {
SSAVar var = reg.getSVar();
int regNum = reg.getRegNum();
RegisterArg resultArg = reg.duplicate(regNum, null);
SSAVar newSsaVar = mth.makeNewSVar(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);
phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar));
}
@Nullable
......@@ -608,10 +712,14 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false;
}
@SuppressWarnings("ForLoopReplaceableByForEach")
private boolean tryToFixIncompatiblePrimitives(MethodNode mth) {
boolean fixed = false;
for (SSAVar var : new ArrayList<>(mth.getSVars())) {
if (processIncompatiblePrimitives(mth, var)) {
List<SSAVar> ssaVars = mth.getSVars();
int ssaVarsCount = ssaVars.size();
// new vars will be added at list end if fix is applied (can't use for-each loop)
for (int i = 0; i < ssaVarsCount; i++) {
if (processIncompatiblePrimitives(mth, ssaVars.get(i))) {
fixed = true;
}
}
......@@ -619,6 +727,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return false;
}
InitCodeVariables.rerun(mth);
initTypeBounds(mth);
return runTypePropagation(mth);
}
......@@ -691,4 +800,32 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
convertInsn.add(AFlag.SYNTHETIC);
return convertInsn;
}
private static void assignImmutableTypes(MethodNode mth) {
for (SSAVar ssaVar : mth.getSVars()) {
ArgType imType = getSsaImmutableType(ssaVar);
if (imType != null) {
ssaVar.getAssign().updateImmutableType(imType);
}
}
}
@Nullable
private static ArgType getSsaImmutableType(SSAVar ssaVar) {
if (ssaVar.isTypeImmutable()) {
ArgType type = ssaVar.getTypeInfo().getType();
if (type != ArgType.UNKNOWN) {
return type;
}
}
if (ssaVar.getAssign().contains(AFlag.IMMUTABLE_TYPE)) {
return ssaVar.getAssign().getInitType();
}
for (RegisterArg reg : ssaVar.getUseList()) {
if (reg.contains(AFlag.IMMUTABLE_TYPE)) {
return reg.getInitType();
}
}
return null;
}
}
......@@ -25,11 +25,6 @@ public class TypeInfo {
return bounds;
}
public void reset() {
this.type = ArgType.UNKNOWN;
this.bounds.clear();
}
@Override
public String toString() {
return "TypeInfo{type=" + type + ", bounds=" + bounds + '}';
......
......@@ -64,7 +64,7 @@ public class TypeSearch {
search(vars);
searchSuccess = fullCheck(vars);
if (Consts.DEBUG_TYPE_INFERENCE && !searchSuccess) {
LOG.warn("Multi-variable search failed in {}", mth);
LOG.debug("Multi-variable search failed in {}", mth);
}
}
if (searchSuccess) {
......@@ -108,7 +108,7 @@ public class TypeSearch {
count *= size;
}
sb.append(" = ").append(count);
LOG.debug("--- count = {}, {}", count, sb);
LOG.debug(" > max iterations count = {}", sb);
}
// prepare vars
......@@ -140,9 +140,15 @@ public class TypeSearch {
}
n++;
if (n > SEARCH_ITERATION_LIMIT) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug(" > iterations limit reached: {}", SEARCH_ITERATION_LIMIT);
}
return false;
}
}
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug(" > done after {} iterations", n);
}
// mark all vars as resolved
for (TypeSearchVarInfo var : vars) {
var.setTypeResolved(true);
......
......@@ -86,7 +86,6 @@ public class TypeSearchVarInfo {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("TypeSearchVarInfo{");
sb.append(var.toShortString());
if (typeResolved) {
sb.append(", resolved type: ").append(currentType);
......@@ -95,7 +94,6 @@ public class TypeSearchVarInfo {
sb.append(", candidateTypes=").append(candidateTypes);
sb.append(", constraints=").append(constraints);
}
sb.append('}');
return sb.toString();
}
}
......@@ -84,8 +84,7 @@ public final class TypeUpdate {
}
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Applying types for {} -> {}", ssaVar, candidateType);
updates.forEach(updateEntry -> LOG.debug(" {} -> {}, insn: {}",
updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn()));
updates.forEach(updateEntry -> LOG.debug(" {} -> {}", updateEntry.getType(), updateEntry.getArg()));
}
updateInfo.applyUpdates();
return CHANGED;
......@@ -96,25 +95,35 @@ public final class TypeUpdate {
throw new JadxRuntimeException("Null type update for arg: " + arg);
}
ArgType currentType = arg.getType();
if (Objects.equals(currentType, candidateType) && !updateInfo.getFlags().isIgnoreSame()) {
return SAME;
}
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
// don't changed type
if (compareResult == TypeCompareEnum.EQUAL) {
if (Objects.equals(currentType, candidateType)) {
if (!updateInfo.getFlags().isIgnoreSame()) {
return SAME;
}
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType);
} else {
if (candidateType.isWildcard()) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Wildcard type rejected for {}: candidate={}, current={}", arg, candidateType, currentType);
}
return REJECT;
}
return REJECT;
}
if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType);
TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType);
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
// don't changed type
if (compareResult == TypeCompareEnum.EQUAL) {
return SAME;
}
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType);
}
return REJECT;
}
if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType);
}
return REJECT;
}
return REJECT;
}
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
......@@ -132,10 +141,7 @@ public final class TypeUpdate {
}
return REJECT;
}
if (!inBounds(updateInfo, typeInfo.getBounds(), candidateType)) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Reject type '{}' for {} by bounds: {}", candidateType, ssaVar, typeInfo.getBounds());
}
if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) {
return REJECT;
}
return requestUpdateForSsaVar(updateInfo, ssaVar, candidateType);
......@@ -191,10 +197,16 @@ public final class TypeUpdate {
}
boolean inBounds(Set<ITypeBound> bounds, ArgType candidateType) {
return inBounds(null, bounds, candidateType);
for (ITypeBound bound : bounds) {
ArgType boundType = bound.getType();
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
return false;
}
}
return true;
}
private boolean inBounds(@Nullable TypeUpdateInfo updateInfo, Set<ITypeBound> bounds, ArgType candidateType) {
private boolean inBounds(TypeUpdateInfo updateInfo, SSAVar ssaVar, Set<ITypeBound> bounds, ArgType candidateType) {
for (ITypeBound bound : bounds) {
ArgType boundType;
if (updateInfo != null && bound instanceof ITypeBoundDynamic) {
......@@ -203,6 +215,9 @@ public final class TypeUpdate {
boundType = bound.getType();
}
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.debug("Reject type '{}' for {} by bound: {}", candidateType, ssaVar, bound);
}
return false;
}
}
......@@ -235,6 +250,7 @@ public final class TypeUpdate {
case UNKNOWN:
LOG.warn("Can't compare types, unknown hierarchy: {} and {}", candidateType, boundType);
comparator.compareTypes(candidateType, boundType);
return true;
default:
......@@ -356,6 +372,9 @@ public final class TypeUpdate {
if (type == null) {
return null;
}
if (type.isWildcard()) {
return null;
}
if (type.containsTypeVariable()) {
if (knownTypeVars.isEmpty()) {
return null;
......
......@@ -21,4 +21,19 @@ public class TypeUpdateFlags {
public boolean isIgnoreSame() {
return ignoreSame;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (allowWider) {
sb.append("ALLOW_WIDER");
}
if (ignoreSame) {
if (sb.length() != 0) {
sb.append('|');
}
sb.append("IGNORE_SAME");
}
return sb.toString();
}
}
......@@ -73,4 +73,9 @@ public class TypeUpdateInfo {
public TypeUpdateFlags getFlags() {
return flags;
}
@Override
public String toString() {
return "TypeUpdateInfo{" + flags + ", updates=" + updates + '}';
}
}
......@@ -12,6 +12,8 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import static jadx.core.utils.Utils.notEmpty;
public class UsageInfo {
private final RootNode root;
......@@ -89,8 +91,8 @@ public class UsageInfo {
if (clsNode != null) {
consumer.accept(clsNode);
}
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
List<ArgType> genericTypes = type.getGenericTypes();
if (type.isGeneric() && notEmpty(genericTypes)) {
for (ArgType argType : genericTypes) {
processType(argType, consumer);
}
......
......@@ -191,7 +191,10 @@ public class BlockUtils {
}
@Nullable
public static BlockNode getBlockByInsn(MethodNode mth, InsnNode insn) {
public static BlockNode getBlockByInsn(MethodNode mth, @Nullable InsnNode insn) {
if (insn == null) {
return null;
}
if (insn instanceof PhiInsn) {
return searchBlockWithPhi(mth, (PhiInsn) insn);
}
......
......@@ -8,7 +8,6 @@ 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;
......@@ -19,17 +18,20 @@ 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.EXCEPTION;
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.SHORT;
import static jadx.core.dex.instructions.args.ArgType.STRING;
import static jadx.core.dex.instructions.args.ArgType.THROWABLE;
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 jadx.core.dex.instructions.args.ArgType.generic;
import static jadx.core.dex.instructions.args.ArgType.genericType;
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;
......@@ -101,8 +103,8 @@ public class TypeCompareTest {
ArgType mapCls = object("java.util.Map");
ArgType setCls = object("java.util.Set");
ArgType keyType = ArgType.genericType("K");
ArgType valueType = ArgType.genericType("V");
ArgType keyType = genericType("K");
ArgType valueType = genericType("V");
ArgType mapGeneric = ArgType.generic(mapCls.getObject(), keyType, valueType);
check(mapCls, mapGeneric, TypeCompareEnum.WIDER_BY_GENERIC);
......@@ -119,6 +121,8 @@ public class TypeCompareTest {
@Test
public void compareWildCards() {
ArgType clsWildcard = generic(CLASS.getObject(), wildcard());
check(clsWildcard, CLASS, TypeCompareEnum.NARROW_BY_GENERIC);
ArgType clsExtendedWildcard = generic(CLASS.getObject(), wildcard(STRING, WildcardBound.EXTENDS));
check(clsWildcard, clsExtendedWildcard, TypeCompareEnum.WIDER);
......@@ -132,28 +136,44 @@ public class TypeCompareTest {
@Test
public void compareGenericTypes() {
ArgType vType = ArgType.genericType("V");
ArgType rType = ArgType.genericType("R");
check(vType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
check(ArgType.OBJECT, vType, TypeCompareEnum.WIDER_BY_GENERIC);
ArgType vType = genericType("V");
check(vType, OBJECT, TypeCompareEnum.NARROW);
check(vType, STRING, TypeCompareEnum.NARROW);
ArgType rType = genericType("R");
check(vType, rType, TypeCompareEnum.CONFLICT);
check(vType, vType, TypeCompareEnum.EQUAL);
ArgType tType = ArgType.genericType("T");
tType.setExtendTypes(Collections.singletonList(ArgType.STRING));
ArgType tType = genericType("T");
ArgType tStringType = genericType("T", STRING);
check(tStringType, STRING, TypeCompareEnum.NARROW);
check(tStringType, OBJECT, TypeCompareEnum.NARROW);
check(tStringType, tType, TypeCompareEnum.NARROW);
ArgType tObjType = genericType("T", OBJECT);
check(tType, ArgType.STRING, TypeCompareEnum.NARROW_BY_GENERIC);
check(tType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
check(tObjType, OBJECT, TypeCompareEnum.NARROW);
check(tObjType, tType, TypeCompareEnum.EQUAL);
check(tStringType, tObjType, TypeCompareEnum.NARROW);
}
@Test
@NotYetImplemented
public void compareGenericTypesNYI() {
ArgType vType = ArgType.genericType("V");
// TODO: use extend types from generic declaration for more strict checks
check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
public void compareGenericTypes2() {
ArgType npeType = object("java.lang.NullPointerException");
// check clsp graph
check(npeType, THROWABLE, TypeCompareEnum.NARROW);
check(npeType, EXCEPTION, TypeCompareEnum.NARROW);
check(EXCEPTION, THROWABLE, TypeCompareEnum.NARROW);
ArgType typeVar = genericType("T", EXCEPTION); // T extends Exception
// target checks
check(THROWABLE, typeVar, TypeCompareEnum.WIDER);
check(EXCEPTION, typeVar, TypeCompareEnum.WIDER);
check(npeType, typeVar, TypeCompareEnum.NARROW);
}
@Test
......
......@@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.GenericTypeParameter;
import jadx.core.dex.nodes.RootNode;
import static org.hamcrest.MatcherAssert.assertThat;
......@@ -30,11 +29,11 @@ class TypeUtilsTest {
@Test
public void testReplaceGenericsWithWildcards() {
// check classpath graph
List<GenericTypeParameter> classGenerics = root.getTypeUtils().getClassGenerics(ArgType.object("java.util.ArrayList"));
List<ArgType> classGenerics = root.getTypeUtils().getClassGenerics(ArgType.object("java.util.ArrayList"));
assertThat(classGenerics, hasSize(1));
GenericTypeParameter genericInfo = classGenerics.get(0);
assertThat(genericInfo.getTypeVariable(), is(ArgType.genericType("E")));
assertThat(genericInfo.getExtendsList(), hasSize(0));
ArgType genericInfo = classGenerics.get(0);
assertThat(genericInfo.getObject(), is("E"));
assertThat(genericInfo.getExtendTypes(), hasSize(0));
// prepare input
ArgType instanceType = ArgType.generic("java.util.ArrayList", ArgType.OBJECT);
......
......@@ -7,7 +7,6 @@ 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.GenericTypeParameter;
import jadx.core.dex.nodes.parser.SignatureParser;
import static jadx.core.dex.instructions.args.ArgType.INT;
......@@ -103,12 +102,12 @@ class SignatureParserTest {
@SuppressWarnings("unchecked")
private static void checkGenerics(String g, Object... objs) {
List<GenericTypeParameter> genericsList = new SignatureParser(g).consumeGenericTypeParameters();
List<GenericTypeParameter> expectedList = new ArrayList<>();
List<ArgType> genericsList = new SignatureParser(g).consumeGenericTypeParameters();
List<ArgType> expectedList = new ArrayList<>();
for (int i = 0; i < objs.length; i += 2) {
ArgType generic = genericType((String) objs[i]);
String typeVar = (String) objs[i];
List<ArgType> list = (List<ArgType>) objs[i + 1];
expectedList.add(new GenericTypeParameter(generic, list));
expectedList.add(ArgType.genericType(typeVar, list));
}
assertThat(genericsList, is(expectedList));
}
......@@ -133,7 +132,7 @@ class SignatureParserTest {
@Test
public void testBadGenericMap() {
List<GenericTypeParameter> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericTypeParameters();
List<ArgType> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericTypeParameters();
assertThat(list, hasSize(0));
}
}
package jadx.tests.integration.types;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
/**
* Issue https://github.com/skylot/jadx/issues/956
*/
public class TestGenerics7 extends IntegrationTest {
public static class TestCls<T> {
private Object[] elements = new Object[1];
@SuppressWarnings("unchecked")
public final T test(int i) {
Object[] arr = this.elements;
T obj = (T) arr[i];
arr[i] = null;
if (obj == null) {
throw new NullPointerException();
}
return obj;
}
public void check() {
this.elements = new Object[] { 1, "" };
assertThat(test(1)).isEqualTo("");
}
}
@Test
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("T t = (T) objArr[i];");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册