Commit ed8c6626 authored by Skylot's avatar Skylot

fix: add generic types propagation (#695)

parent 850df18d
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
dependencies {
runtime files(jadxClasspath)
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
......
......@@ -12,6 +12,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -22,9 +23,11 @@ import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
......@@ -55,7 +58,16 @@ public class ClsSet {
private NClass[] classes;
public void load(RootNode root) {
public void loadFromClstFile() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
......@@ -68,7 +80,8 @@ public class ClsSet {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setMethods(loadMethods(cls, nClass));
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
}
......@@ -88,45 +101,43 @@ public class ClsSet {
}
}
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
private List<NMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isPublic()
&& !m.getAccessFlags().isProtected()) {
continue;
AccessInfo accessFlags = m.getAccessFlags();
if (accessFlags.isPublic() || accessFlags.isProtected()) {
processMethodDetails(methods, m, accessFlags);
}
}
return methods;
}
List<ArgType> args = new ArrayList<>();
boolean genericArg = false;
for (RegisterArg r : m.getArguments(false)) {
ArgType argType = r.getType();
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<RegisterArg> args = mth.getArguments(false);
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++) {
RegisterArg arg = args.get(i);
ArgType argType = arg.getType();
if (argType.isGeneric() || argType.isGenericType()) {
args.add(argType);
genericArgs[i] = argType;
genericArg = true;
} else {
args.add(null);
}
}
ArgType retType = m.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = m.getAccessFlags().isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(
m.getMethodInfo().getShortId(),
args.isEmpty()
? new ArgType[0]
: args.toArray(new ArgType[args.size()]),
retType,
varArgs));
}
}
return methods.toArray(new NMethod[methods.size()]);
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
}
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
......@@ -207,42 +218,58 @@ public class ClsSet {
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
writeMethod(out, method, names);
}
}
}
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
int argCount = 0;
ArgType[] argTypes = method.getArgType();
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
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());
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);
ArgType[] argTypes = method.getGenericArgs();
if (argTypes == null) {
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);
}
}
}
if (method.getReturnType() != null) {
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
} else {
out.writeBoolean(false);
}
out.writeBoolean(method.isVarArgs());
}
......@@ -283,16 +310,7 @@ public class ClsSet {
}
}
public void load() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}
public void load(File input) throws IOException, DecodeException {
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
......@@ -313,7 +331,7 @@ public class ClsSet {
}
}
public void load(InputStream input) throws IOException, DecodeException {
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
......@@ -335,16 +353,44 @@ public class ClsSet {
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
}
}
}
int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<GenericInfo> 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));
}
classes[i].setMethods(methods);
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}
private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in));
}
return methods;
}
private NMethod readMethod(DataInputStream in) throws IOException {
......@@ -372,6 +418,7 @@ public class ClsSet {
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), bounds);
case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
......@@ -385,34 +432,20 @@ public class ClsSet {
}
}
return ArgType.generic(obj, generics);
case GENERIC_TYPE:
return ArgType.genericType(readString(in));
case OBJECT:
return ArgType.object(classes[in.readInt()].getName());
case ARRAY:
return ArgType.array(readArgType(in));
case PRIMITIVE:
int shortName = in.readByte();
switch (shortName) {
case 'Z':
return ArgType.BOOLEAN;
case 'C':
return ArgType.CHAR;
case 'B':
return ArgType.BYTE;
case 'S':
return ArgType.SHORT;
case 'I':
return ArgType.INT;
case 'F':
return ArgType.FLOAT;
case 'J':
return ArgType.LONG;
case 'D':
return ArgType.DOUBLE;
default:
return ArgType.VOID;
}
char shortName = (char) in.readByte();
return ArgType.parse(shortName);
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
......
......@@ -10,15 +10,18 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
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.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
......@@ -30,7 +33,7 @@ public class ClspGraph {
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
set.loadFromClstFile();
addClasspath(set);
}
......@@ -62,6 +65,19 @@ public class ClspGraph {
return nameMap.containsKey(fullName);
}
public NClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}
@Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) {
return null;
}
return cls.getMethodsMap().get(methodInfo.getShortId());
}
private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
......
......@@ -49,7 +49,7 @@ public class ConvertToClsSet {
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}", output);
LOG.info("done");
......
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 NClass[] parents;
private NMethod[] methods;
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;
......@@ -31,6 +41,37 @@ public class NClass {
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();
......@@ -52,12 +93,4 @@ public class NClass {
public String toString() {
return name;
}
public void setMethods(NMethod[] methods) {
this.methods = methods;
}
public NMethod[] getMethods() {
return methods;
}
}
package jadx.core.clsp;
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 {
public class NMethod implements Comparable<NMethod> {
private final String shortId;
private final ArgType[] argType;
/**
* 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, ArgType[] argType, ArgType retType, boolean varArgs) {
public NMethod(String shortId, @Nullable ArgType[] genericArgs, @Nullable ArgType retType, boolean varArgs) {
this.shortId = shortId;
this.argType = argType;
this.genericArgs = genericArgs;
this.retType = retType;
this.varArgs = varArgs;
}
......@@ -23,10 +34,21 @@ public class NMethod {
return shortId;
}
public ArgType[] getArgType() {
return argType;
@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;
}
......@@ -34,4 +56,35 @@ public class NMethod {
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=" + genericArgs
+ ", retType=" + retType
+ ", varArgs=" + varArgs
+ '}';
}
}
......@@ -5,8 +5,6 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
......@@ -27,6 +25,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.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
......@@ -143,7 +142,7 @@ public class ClassGen {
clsCode.attachDefinition(cls);
clsCode.add(cls.getClassInfo().getAliasShortName());
addGenericMap(clsCode, cls.getGenericMap(), true);
addGenericMap(clsCode, cls.getGenerics(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
......@@ -174,23 +173,23 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
if (gmap == null || gmap.isEmpty()) {
public boolean addGenericMap(CodeWriter code, List<GenericInfo> generics, boolean classDeclaration) {
if (generics == null || generics.isEmpty()) {
return false;
}
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
for (GenericInfo genericInfo : generics) {
if (i != 0) {
code.add(", ");
}
ArgType type = genericInfo.getGenericType();
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, type);
}
List<ArgType> list = genericInfo.getExtendsList();
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
......
......@@ -99,7 +99,7 @@ public class MethodGen {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
if (classGen.addGenericMap(code, mth.getGenerics(), false)) {
code.add(' ');
}
if (ai.isConstructor()) {
......
......@@ -622,6 +622,24 @@ public abstract class ArgType {
return 1;
}
public boolean containsGenericType() {
if (isGenericType()) {
return true;
}
if (isGeneric()) {
ArgType[] genericTypes = getGenericTypes();
if (genericTypes != null) {
for (ArgType genericType : genericTypes) {
if (genericType.containsGenericType()) {
return true;
}
}
}
return false;
}
return false;
}
public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) {
if (!type.isObject() || type.isGenericType()) {
return type;
......
......@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
......@@ -109,6 +110,18 @@ public abstract class InsnArg extends Typed {
if (i == -1) {
return null;
}
if (insn.getType() == InsnType.MOVE && this.isRegister()) {
// preserve variable name for move insn (needed in `for-each` loop for iteration variable)
String name = ((RegisterArg) this).getName();
if (name != null) {
InsnArg arg = insn.getArg(0);
if (arg.isRegister()) {
((RegisterArg) arg).setNameIfUnknown(name);
} else if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getResult().setNameIfUnknown(name);
}
}
}
insn.add(AFlag.WRAPPED);
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
......
......@@ -97,6 +97,12 @@ public class RegisterArg extends InsnArg implements Named {
}
}
public void setNameIfUnknown(String name) {
if (getName() == null) {
setName(name);
}
}
public boolean isNameEquals(InsnArg arg) {
String n = getName();
if (n == null || !(arg instanceof Named)) {
......
......@@ -46,7 +46,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private AccessInfo accessFlags;
private ArgType superClass;
private List<ArgType> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private List<GenericInfo> generics = Collections.emptyList();
private final List<MethodNode> methods;
private final List<FieldNode> fields;
......@@ -180,7 +180,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
}
try {
// parse class generic map
genericMap = sp.consumeGenericMap();
generics = sp.consumeGenericMap();
// parse super class signature
superClass = sp.consumeType();
// parse interfaces signatures
......@@ -283,8 +283,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return interfaces;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
public List<GenericInfo> getGenerics() {
return generics;
}
public List<MethodNode> getMethods() {
......
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 + '}';
}
}
......@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
......@@ -67,7 +66,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars;
private Map<ArgType, List<ArgType>> genericMap;
private List<GenericInfo> generics;
private List<BlockNode> blocks;
private BlockNode enterBlock;
......@@ -95,7 +94,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
// don't unload retType and argsList, will be used in jadx-gui after class unload
thisArg = null;
sVars = Collections.emptyList();
genericMap = null;
generics = Collections.emptyList();
instructions = null;
blocks = null;
enterBlock = null;
......@@ -174,7 +173,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return false;
}
try {
genericMap = sp.consumeGenericMap();
generics = sp.consumeGenericMap();
List<ArgType> argsTypes = sp.consumeMethodArgs();
retType = sp.consumeType();
......@@ -261,8 +260,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return retType;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
public List<GenericInfo> getGenerics() {
return generics;
}
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
......
......@@ -13,16 +13,19 @@ import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
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.visitors.typeinference.TypeUpdate;
import jadx.core.utils.CacheStorage;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
......@@ -191,6 +194,31 @@ public class RootNode {
return cls.dex().deepResolveField(cls, field);
}
@Nullable
public ArgType getMethodGenericReturnType(MethodInfo callMth) {
MethodNode methodNode = deepResolveMethod(callMth);
if (methodNode != null) {
ArgType returnType = methodNode.getReturnType();
if (returnType == null) {
try {
methodNode.load();
returnType = methodNode.getReturnType();
} catch (DecodeException e) {
LOG.error("Method load error", e);
}
}
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<DexNode> getDexNodes() {
return dexNodes;
}
......
package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -13,6 +12,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.utils.exceptions.JadxRuntimeException;
public class SignatureParser {
......@@ -219,11 +219,11 @@ public class SignatureParser {
* <p/>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
*/
public Map<ArgType, List<ArgType>> consumeGenericMap() {
public List<GenericInfo> consumeGenericMap() {
if (!lookAhead('<')) {
return Collections.emptyMap();
return Collections.emptyList();
}
Map<ArgType, List<ArgType>> map = new LinkedHashMap<>(2);
List<GenericInfo> list = new ArrayList<>();
consume('<');
while (true) {
if (lookAhead('>') || next() == STOP_CHAR) {
......@@ -231,15 +231,15 @@ public class SignatureParser {
}
String id = consumeUntil(':');
if (id == null) {
LOG.error("Can't parse generic map: {}", sign);
return Collections.emptyMap();
LOG.error("Failed to parse generic map: {}", sign);
return Collections.emptyList();
}
tryConsume(':');
List<ArgType> types = consumeExtendsTypesList();
map.put(ArgType.genericType(id), types);
list.add(new GenericInfo(ArgType.genericType(id), types));
}
consume('>');
return map;
return list;
}
/**
......
......@@ -259,11 +259,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
private void addMethodMsg(MethodNode mth) {
if (commentedCount > 0) {
String msg = "JADX WARN: Illegal instructions before constructor call commented (this can break semantics)";
String msg = "Illegal instructions before constructor call commented (this can break semantics)";
if (brokenCode || regionDepth > 1) {
mth.addWarn(msg);
} else {
mth.addComment(msg);
mth.addComment("JADX WARN: " + msg);
}
}
}
......
......@@ -327,7 +327,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
LOG.warn("Generic type differs: '{}' and '{}' in {}", gType, varType, mth);
return false;
}
if (!iterableArg.isRegister()) {
if (!iterableArg.isRegister() || !iterableType.isObject()) {
return true;
}
// TODO: add checks
......
......@@ -26,6 +26,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
......@@ -64,19 +66,25 @@ public class ProcessVariables extends AbstractVisitor {
private void checkCodeVars(MethodNode mth, List<CodeVar> codeVars) {
int unknownTypesCount = 0;
for (CodeVar codeVar : codeVars) {
codeVar.getSsaVars().stream()
.filter(ssaVar -> ssaVar.contains(AFlag.IMMUTABLE_TYPE))
.forEach(ssaVar -> {
ArgType ssaType = ssaVar.getAssign().getInitType();
if (ssaType.isTypeKnown() && !ssaType.equals(codeVar.getType())) {
mth.addWarn("Incorrect type for immutable var: ssa=" + ssaType
+ ", code=" + codeVar.getType()
+ ", for " + ssaVar.getDetailedVarInfo(mth));
}
});
if (codeVar.getType() == null) {
ArgType codeVarType = codeVar.getType();
if (codeVarType == null) {
codeVar.setType(ArgType.UNKNOWN);
unknownTypesCount++;
} else {
codeVar.getSsaVars().stream()
.filter(ssaVar -> ssaVar.contains(AFlag.IMMUTABLE_TYPE))
.forEach(ssaVar -> {
ArgType ssaType = ssaVar.getAssign().getInitType();
if (ssaType.isTypeKnown()) {
TypeCompare comparator = mth.root().getTypeUpdate().getComparator();
TypeCompareEnum result = comparator.compareTypes(ssaType, codeVarType);
if (result == TypeCompareEnum.CONFLICT || result.isNarrow()) {
mth.addWarn("Incorrect type for immutable var: ssa=" + ssaType
+ ", code=" + codeVarType
+ ", for " + ssaVar.getDetailedVarInfo(mth));
}
}
});
}
}
if (unknownTypesCount != 0) {
......
......@@ -5,7 +5,11 @@ import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
/**
* Information to restrict types by applying constraints (or boundaries)
*/
public interface ITypeBound {
BoundEnum getBound();
ArgType getType();
......
package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.instructions.args.ArgType;
/**
* 'Dynamic' type bound allows to use requested and not yet applied types
* from {@link TypeUpdateInfo} for more precise restrictions
*/
public interface ITypeBoundDynamic extends ITypeBound {
/**
* This method will be executed instead of {@link ITypeBound#getType()}
* if {@link TypeUpdateInfo} is available.
*/
ArgType getType(TypeUpdateInfo updateInfo);
}
package jadx.core.dex.visitors.typeinference;
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;
/**
* Special dynamic bound for invoke with generics.
* Bound type calculated using instance generic type.
* TODO: also can depends on argument types
*/
public final class TypeBoundInvokeAssign implements ITypeBoundDynamic {
private final RootNode root;
private final InvokeNode invokeNode;
private final ArgType genericReturnType;
public TypeBoundInvokeAssign(RootNode root, InvokeNode invokeNode, ArgType genericReturnType) {
this.root = root;
this.invokeNode = invokeNode;
this.genericReturnType = genericReturnType;
}
@Override
public BoundEnum getBound() {
return BoundEnum.ASSIGN;
}
@Override
public ArgType getType(TypeUpdateInfo updateInfo) {
return getReturnType(updateInfo.getType(invokeNode.getArg(0)));
}
@Override
public ArgType getType() {
return getReturnType(invokeNode.getArg(0).getType());
}
private ArgType getReturnType(ArgType instanceType) {
ArgType resultGeneric = TypeUpdate.getResultGeneric(root, instanceType, genericReturnType);
if (resultGeneric != null) {
return resultGeneric;
}
return invokeNode.getCallMth().getReturnType();
}
@Override
public RegisterArg getArg() {
return invokeNode.getResult();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TypeBoundInvokeAssign that = (TypeBoundInvokeAssign) o;
return invokeNode.equals(that.invokeNode);
}
@Override
public int hashCode() {
return invokeNode.hashCode();
}
@Override
public String toString() {
return "InvokeAssign{" + invokeNode.getCallMth().getShortId()
+ ", returnType=" + genericReturnType
+ ", currentType=" + getType()
+ ", instanceArg=" + invokeNode.getArg(0)
+ '}';
}
}
......@@ -16,8 +16,11 @@ 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.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
......@@ -51,11 +54,13 @@ import jadx.core.utils.Utils;
public final class TypeInferenceVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(TypeInferenceVisitor.class);
private RootNode root;
private TypeUpdate typeUpdate;
@Override
public void init(RootNode root) {
typeUpdate = root.getTypeUpdate();
this.root = root;
this.typeUpdate = root.getTypeUpdate();
}
@Override
......@@ -239,6 +244,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
break;
case INVOKE:
addBound(typeInfo, makeAssignInvokeBound((InvokeNode) insn));
break;
default:
ArgType type = insn.getResult().getInitType();
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, type));
......@@ -246,6 +255,24 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
}
private ITypeBound makeAssignInvokeBound(InvokeNode invokeNode) {
MethodInfo callMth = invokeNode.getCallMth();
ArgType boundType = callMth.getReturnType();
ArgType genericReturnType = root.getMethodGenericReturnType(callMth);
if (genericReturnType != null) {
if (genericReturnType.containsGenericType()) {
InvokeType invokeType = invokeNode.getInvokeType();
if (invokeNode.getArgsCount() != 0
&& invokeType != InvokeType.STATIC && invokeType != InvokeType.SUPER) {
return new TypeBoundInvokeAssign(root, invokeNode, genericReturnType);
}
} else {
boundType = genericReturnType;
}
}
return new TypeBoundConst(BoundEnum.ASSIGN, boundType);
}
@Nullable
private ITypeBound makeUseBound(RegisterArg regArg) {
InsnNode insn = regArg.getParentInsn();
......
......@@ -2,23 +2,29 @@ package jadx.core.dex.visitors.typeinference;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.clsp.NClass;
import jadx.core.dex.attributes.AFlag;
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;
import jadx.core.dex.instructions.args.InsnArg;
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.GenericInfo;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxOverflowException;
......@@ -34,10 +40,12 @@ public final class TypeUpdate {
private static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags();
private static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags().allowWider();
private final RootNode root;
private final Map<InsnType, ITypeListener> listenerRegistry;
private final TypeCompare comparator;
public TypeUpdate(RootNode root) {
this.root = root;
this.listenerRegistry = initListenerRegistry();
this.comparator = new TypeCompare(root);
}
......@@ -74,7 +82,7 @@ public final class TypeUpdate {
LOG.debug("Applying types, init for {} -> {}", ssaVar, candidateType);
updates.forEach(updateEntry -> LOG.debug(" {} -> {}", updateEntry.getType(), updateEntry.getArg()));
}
updates.forEach(TypeUpdateEntry::apply);
updateInfo.applyUpdates();
return CHANGED;
}
......@@ -112,7 +120,7 @@ public final class TypeUpdate {
private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
TypeInfo typeInfo = ssaVar.getTypeInfo();
if (!inBounds(typeInfo.getBounds(), candidateType)) {
if (!inBounds(updateInfo, typeInfo.getBounds(), candidateType)) {
if (Consts.DEBUG) {
LOG.debug("Reject type '{}' for {} by bounds: {}", candidateType, ssaVar, typeInfo.getBounds());
}
......@@ -176,8 +184,17 @@ public final class TypeUpdate {
}
boolean inBounds(Set<ITypeBound> bounds, ArgType candidateType) {
return inBounds(null, bounds, candidateType);
}
private boolean inBounds(@Nullable TypeUpdateInfo updateInfo, Set<ITypeBound> bounds, ArgType candidateType) {
for (ITypeBound bound : bounds) {
ArgType boundType = bound.getType();
ArgType boundType;
if (updateInfo != null && bound instanceof ITypeBoundDynamic) {
boundType = ((ITypeBoundDynamic) bound).getType(updateInfo);
} else {
boundType = bound.getType();
}
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
return false;
}
......@@ -185,10 +202,10 @@ public final class TypeUpdate {
return true;
}
private boolean inBounds(InsnArg arg, ArgType candidateType) {
private boolean inBounds(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
if (arg.isRegister()) {
TypeInfo typeInfo = ((RegisterArg) arg).getSVar().getTypeInfo();
return inBounds(typeInfo.getBounds(), candidateType);
return inBounds(updateInfo, typeInfo.getBounds(), candidateType);
}
return arg.getType().equals(candidateType);
}
......@@ -258,9 +275,83 @@ public final class TypeUpdate {
registry.put(InsnType.NEG, this::suggestAllSameListener);
registry.put(InsnType.NOT, this::suggestAllSameListener);
registry.put(InsnType.CHECK_CAST, this::checkCastListener);
registry.put(InsnType.INVOKE, this::invokeListener);
return registry;
}
private TypeUpdateResult invokeListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
if (insn.getResult() == null) {
return SAME;
}
if (candidateType.isGeneric() || candidateType.isGenericType()) {
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);
if (returnType == null) {
return SAME;
}
ArgType resultGeneric = getResultGeneric(root, candidateType, returnType);
if (resultGeneric == null) {
return SAME;
}
return updateTypeChecked(updateInfo, insn.getResult(), resultGeneric);
}
}
return SAME;
}
@Nullable
public static ArgType getResultGeneric(RootNode root, ArgType instanceType, ArgType genericRetType) {
if (genericRetType == null) {
return null;
}
if (instanceType.isGeneric()) {
NClass clsDetails = root.getClsp().getClsDetails(instanceType);
if (clsDetails == null || clsDetails.getGenerics().isEmpty()) {
return null;
}
List<GenericInfo> generics = clsDetails.getGenerics();
ArgType[] actualTypes = instanceType.getGenericTypes();
if (generics.size() != actualTypes.length) {
return null;
}
Map<ArgType, ArgType> replaceMap = new LinkedHashMap<>();
for (int i = 0; i < actualTypes.length; i++) {
ArgType actualType = actualTypes[i];
ArgType genericType = generics.get(i).getGenericType();
replaceMap.put(genericType, actualType);
}
return replaceGenericTypes(genericRetType, replaceMap);
}
return null;
}
private static ArgType replaceGenericTypes(ArgType replaceType, Map<ArgType, ArgType> replaceMap) {
if (replaceType.isGenericType()) {
return replaceMap.get(replaceType);
}
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 = replaceGenericTypes(genericType, replaceMap);
if (type == null) {
type = genericType;
}
newTypes[i] = type;
}
return ArgType.generic(replaceType.getObject(), newTypes);
}
return null;
}
private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
return updateTypeChecked(updateInfo, changeArg, candidateType);
......@@ -275,7 +366,7 @@ public final class TypeUpdate {
TypeCompareEnum compareTypes = comparator.compareTypes(candidateType, changeArg.getType());
boolean correctType = compareTypes == TypeCompareEnum.EQUAL
|| (assignChanged ? compareTypes.isWider() : compareTypes.isNarrow());
if (correctType && inBounds(changeArg, candidateType)) {
if (correctType && inBounds(updateInfo, changeArg, candidateType)) {
allowReject = true;
} else {
return REJECT;
......
......@@ -12,10 +12,6 @@ public final class TypeUpdateEntry {
this.type = type;
}
public void apply() {
arg.setType(type);
}
public InsnArg getArg() {
return arg;
}
......
......@@ -18,6 +18,13 @@ public class TypeUpdateInfo {
updates.add(new TypeUpdateEntry(arg, changeType));
}
public void applyUpdates() {
for (TypeUpdateEntry updateEntry : updates) {
InsnArg arg = updateEntry.getArg();
arg.setType(updateEntry.getType());
}
}
public boolean isProcessed(InsnArg arg) {
if (updates.isEmpty()) {
return false;
......@@ -30,6 +37,15 @@ public class TypeUpdateInfo {
return false;
}
public ArgType getType(InsnArg arg) {
for (TypeUpdateEntry update : updates) {
if (update.getArg() == arg) {
return update.getType();
}
}
return arg.getType();
}
public void rollbackUpdate(InsnArg arg) {
updates.removeIf(updateEntry -> updateEntry.getArg() == arg);
}
......
......@@ -353,7 +353,7 @@ public abstract class IntegrationTest extends TestUtils {
return dynamicCompiler.invoke(cls, methodName, types, args);
}
public File getJarForClass(Class<?> cls) throws IOException {
private File getJarForClass(Class<?> cls) throws IOException {
List<File> files = compileClass(cls);
assertThat("File list is empty", files, not(empty()));
......
package jadx.tests.functional;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.parser.SignatureParser;
import static jadx.core.dex.instructions.args.ArgType.INT;
......@@ -20,7 +20,6 @@ import static jadx.core.dex.instructions.args.ArgType.wildcard;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
......@@ -92,14 +91,14 @@ class SignatureParserTest {
@SuppressWarnings("unchecked")
private static void checkGenerics(String g, Object... objs) {
Map<ArgType, List<ArgType>> map = new SignatureParser(g).consumeGenericMap();
Map<ArgType, List<ArgType>> expectedMap = new LinkedHashMap<>();
List<GenericInfo> genericsList = new SignatureParser(g).consumeGenericMap();
List<GenericInfo> 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];
expectedMap.put(generic, list);
expectedList.add(new GenericInfo(generic, list));
}
assertThat(map, is(expectedMap));
assertThat(genericsList, is(expectedList));
}
@Test
......@@ -122,7 +121,7 @@ class SignatureParserTest {
@Test
public void testBadGenericMap() {
Map<ArgType, List<ArgType>> map = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericMap();
assertThat(map, anEmptyMap());
List<GenericInfo> list = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericMap();
assertThat(list, hasSize(0));
}
}
......@@ -32,6 +32,6 @@ public class TestNestedLoops2 extends IntegrationTest {
String code = cls.getCode().toString();
assertThat(code, containsOne("for (int i = 0; i < list.size(); i++) {"));
assertThat(code, containsOne("while (j < ((String) list.get(i)).length()) {"));
assertThat(code, containsOne("while (j < list.get(i).length()) {"));
}
}
package jadx.tests.integration.types;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestGenerics2 extends SmaliTest {
// @formatter:off
/*
public void test() {
Map<Integer, String> map = this.field;
useInt(map.size());
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, String> next = it.next();
useInt(next.getKey().intValue());
next.getValue().trim();
}
}
*/
// @formatter:on
@Test
public void test() {
ClassNode cls = getClassNodeFromSmali();
String code = cls.getCode().toString();
assertThat(code, containsOne("Entry<Integer, String> next"));
assertThat(code, containsOne("useInt(next.getKey().intValue());")); // no Integer cast
}
}
package jadx.tests.integration.types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestGenerics3 extends IntegrationTest {
public static class TestCls {
public static void test() {
List<String> classes = getClasses();
Collections.sort(classes);
int passed = 0;
for (String cls : classes) {
if (runTest(cls)) {
passed++;
}
}
int failed = classes.size() - passed;
System.out.println("failed: " + failed);
}
private static boolean runTest(String clsName) {
return false;
}
private static List<String> getClasses() {
return new ArrayList<>();
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("List<String> classes"));
assertThat(code, containsOne("for (String cls : classes) {"));
}
@Test
public void testNoDebug() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("List<String> classes"));
}
}
.class public final Ltypes/TestGenerics2;
.super Ljava/lang/Object;
.source "SourceFile"
# instance fields
.field private field:Ljava/util/Map;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/Map<",
"Ljava/lang/Integer;",
"Ljava/lang/String;",
">;"
}
.end annotation
.end field
.method public test()V
.registers 5
iget-object v4, p0, Ltypes/TestGenerics2;->field:Ljava/util/Map;
invoke-interface {v4}, Ljava/util/Map;->size()I
move-result v0
invoke-static {v0}, Ltypes/TestGenerics2;->useInt(I)V
invoke-interface {v4}, Ljava/util/Map;->entrySet()Ljava/util/Set;
move-result-object v4
invoke-interface {v4}, Ljava/util/Set;->iterator()Ljava/util/Iterator;
move-result-object v4
:goto_16
invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z
move-result v0
if-eqz v0, :ret
invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v0
invoke-interface {v0}, Ljava/util/Map$Entry;->getKey()Ljava/lang/Object;
move-result-object v1
check-cast v1, Ljava/lang/Integer;
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
invoke-static {v1}, Ltypes/TestGenerics2;->useInt(I)V
invoke-interface {v0}, Ljava/util/Map$Entry;->getValue()Ljava/lang/Object;
move-result-object v0
check-cast v0, Ljava/lang/String;
invoke-interface {v0, p1}, Ljava/lang/String;->trim()Ljava/lang/String;
goto :goto_16
:ret
return-void
.end method
.method public static useInt(I)V
.registers 3
return-void
.end method
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment