diff --git a/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java b/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java index 2ecd2f4d6..c962af7bd 100644 --- a/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java +++ b/jadx-cli/src/main/java/jadx/cli/clst/ConvertToClsSet.java @@ -23,8 +23,9 @@ public class ConvertToClsSet { private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class); public static void usage() { - LOG.info(" "); + LOG.info(" "); LOG.info("Arguments to update core.jcst: " + + " " + "/jadx-core/src/main/resources/clst/core.jcst " + "/platforms/android-/android.jar" + "/platforms/android-/optional/android.car.jar " @@ -32,11 +33,12 @@ public class ConvertToClsSet { } public static void main(String[] args) { - if (args.length < 2) { + if (args.length != 5) { usage(); System.exit(1); } - List inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList()); + int androidApiLevel = Integer.parseInt(args[0]); + List inputPaths = Stream.of(args).skip(1).map(Paths::get).collect(Collectors.toList()); Path output = inputPaths.remove(0); JadxArgs jadxArgs = new JadxArgs(); @@ -57,6 +59,7 @@ public class ConvertToClsSet { decompiler.load(); RootNode root = decompiler.getRoot(); ClsSet set = new ClsSet(root); + set.setAndroidApiLevel(androidApiLevel); set.loadFrom(root); set.save(output); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index 4010acd26..55408ecc8 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -44,14 +44,17 @@ public class ClsSet { private static final String CLST_PATH = "/clst/" + CLST_FILENAME; private static final String JADX_CLS_SET_HEADER = "jadx-cst"; - private static final int VERSION = 4; + private static final int VERSION = 5; private static final String STRING_CHARSET = "US-ASCII"; private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0]; + private static final ArgType[] OBJECT_ARGTYPE_ARRAY = new ArgType[] { ArgType.OBJECT }; private final RootNode root; + private int androidApiLevel; + public ClsSet(RootNode root) { this.root = root; } @@ -79,7 +82,8 @@ public class ClsSet { if (LOG.isDebugEnabled()) { long time = System.currentTimeMillis() - startTime; int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum(); - LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount); + LOG.debug("Clst file loaded in {}ms, android api: {}, classes: {}, methods: {}", + time, androidApiLevel, classes.length, methodsCount); } } @@ -93,7 +97,7 @@ public class ClsSet { cls.load(); ClspClassSource source = getClspClassSource(cls); - ClspClass nClass = new ClspClass(clsType, k, source); + ClspClass nClass = new ClspClass(clsType, k, cls.getAccessFlags().rawValue(), source); if (names.put(clsRawName, nClass) != null) { throw new JadxRuntimeException("Duplicate class: " + clsRawName); } @@ -151,7 +155,11 @@ public class ClsSet { // cls is java.lang.Object return EMPTY_ARGTYPE_ARRAY; } - ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()]; + int interfacesCount = cls.getInterfaces().size(); + if (interfacesCount == 0 && superClass == ArgType.OBJECT) { + return OBJECT_ARGTYPE_ARRAY; + } + ArgType[] parents = new ArgType[1 + interfacesCount]; parents[0] = superClass; int k = 1; for (ArgType iface : cls.getInterfaces()) { @@ -193,10 +201,12 @@ public class ClsSet { DataOutputStream out = new DataOutputStream(output); out.writeBytes(JADX_CLS_SET_HEADER); out.writeByte(VERSION); + out.writeInt(androidApiLevel); Map names = new HashMap<>(classes.length); out.writeInt(classes.length); for (ClspClass cls : classes) { + out.writeInt(cls.getAccFlags()); writeUnsignedByte(out, cls.getSource().ordinal()); String clsName = cls.getName(); writeString(out, clsName); @@ -243,6 +253,10 @@ public class ClsSet { out.writeByte(-1); return; } + if (arr == OBJECT_ARGTYPE_ARRAY) { + out.writeByte(-2); + return; + } int size = arr.length; out.writeByte(size); if (size != 0) { @@ -294,22 +308,22 @@ public class ClsSet { try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) { byte[] header = new byte[JADX_CLS_SET_HEADER.length()]; int readHeaderLength = in.read(header); - int version = in.readByte(); if (readHeaderLength != JADX_CLS_SET_HEADER.length() - || !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET)) - || version != VERSION) { + || !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))) { throw new DecodeException("Wrong jadx class set header"); } + int version = in.readByte(); + if (version != VERSION) { + throw new DecodeException("Wrong jadx class set version, got: " + version + ", expect: " + VERSION); + } + androidApiLevel = in.readInt(); int clsCount = in.readInt(); classes = new ClspClass[clsCount]; - ClspClassSource[] clspClassSources = ClspClassSource.values(); for (int i = 0; i < clsCount; i++) { - int source = readUnsignedByte(in); - if (source < 0 || source > clspClassSources.length) { - throw new DecodeException("Wrong jadx source identifier"); - } + int accFlags = in.readInt(); + ClspClassSource clsSource = readClsSource(in); String name = readString(in); - classes[i] = new ClspClass(ArgType.object(name), i, clspClassSources[source]); + classes[i] = new ClspClass(ArgType.object(name), i, accFlags, clsSource); } for (int i = 0; i < clsCount; i++) { ClspClass nClass = classes[i]; @@ -321,6 +335,15 @@ public class ClsSet { } } + private static ClspClassSource readClsSource(DataInputStream in) throws IOException, DecodeException { + int source = readUnsignedByte(in); + ClspClassSource[] clspClassSources = ClspClassSource.values(); + if (source < 0 || source > clspClassSources.length) { + throw new DecodeException("Wrong jadx source identifier: " + source); + } + return clspClassSources[source]; + } + private List readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException { int mCount = in.readShort(); List methods = new ArrayList<>(mCount); @@ -366,17 +389,20 @@ public class ClsSet { @Nullable private ArgType[] readArgTypesArray(DataInputStream in) throws IOException { int count = in.readByte(); - if (count == -1) { - return null; + switch (count) { + case -1: + return null; + case -2: + return OBJECT_ARGTYPE_ARRAY; + case 0: + return EMPTY_ARGTYPE_ARRAY; + default: + ArgType[] arr = new ArgType[count]; + for (int i = 0; i < count; i++) { + arr[i] = readArgType(in); + } + return arr; } - if (count == 0) { - return EMPTY_ARGTYPE_ARRAY; - } - ArgType[] arr = new ArgType[count]; - for (int i = 0; i < count; i++) { - arr[i] = readArgType(in); - } - return arr; } private ArgType readArgType(DataInputStream in) throws IOException { @@ -384,9 +410,6 @@ public class ClsSet { if (ordinal == -1) { return null; } - if (ordinal >= TypeEnum.values().length) { - throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal); - } switch (TypeEnum.values()[ordinal]) { case WILDCARD: ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte()); @@ -414,7 +437,7 @@ public class ClsSet { return classes[in.readInt()].getClsType(); case ARRAY: - return ArgType.array(readArgType(in)); + return ArgType.array(Objects.requireNonNull(readArgType(in))); case PRIMITIVE: char shortName = (char) in.readByte(); @@ -474,4 +497,12 @@ public class ClsSet { nameMap.put(cls.getName(), cls); } } + + public int getAndroidApiLevel() { + return androidApiLevel; + } + + public void setAndroidApiLevel(int androidApiLevel) { + this.androidApiLevel = androidApiLevel; + } } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java b/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java index ef5a9586b..30ac8b727 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspClass.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.instructions.args.ArgType; /** @@ -16,21 +17,17 @@ public class ClspClass { private final ArgType clsType; private final int id; + private final int accFlags; private ArgType[] parents; private Map methodsMap = Collections.emptyMap(); private List typeParameters = Collections.emptyList(); private ClspClassSource source; - public ClspClass(ArgType clsType, int id) { - this.clsType = clsType; - this.id = id; - this.source = ClspClassSource.APP; - } - - public ClspClass(ArgType clsType, int id, ClspClassSource source) { + public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) { this.clsType = clsType; this.id = id; + this.accFlags = accFlags; this.source = source; } @@ -46,6 +43,14 @@ public class ClspClass { return id; } + public int getAccFlags() { + return accFlags; + } + + public boolean isInterface() { + return AccessFlags.hasFlag(accFlags, AccessFlags.INTERFACE); + } + public ArgType[] getParents() { return parents; } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index 9fb7c6d7d..3579f762c 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.core.Consts; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; @@ -106,7 +107,7 @@ public class ClspGraph { private void addClass(ClassNode cls) { ArgType clsType = cls.getClassInfo().getType(); String rawName = clsType.getObject(); - ClspClass clspClass = new ClspClass(clsType, -1); + ClspClass clspClass = new ClspClass(clsType, -1, cls.getAccessFlags().rawValue(), ClspClassSource.APP); clspClass.setParents(ClsSet.makeParentsArray(cls)); nameMap.put(rawName, clspClass); } @@ -174,6 +175,8 @@ public class ClspGraph { return result == null ? Collections.emptySet() : result; } + private static final Set OBJECT_SINGLE_SET = Collections.singleton(Consts.CLASS_OBJECT); + private void fillSuperTypesCache() { Map> map = new HashMap<>(nameMap.size()); Set tmpSet = new HashSet<>(); @@ -182,10 +185,25 @@ public class ClspGraph { tmpSet.clear(); addSuperTypes(cls, tmpSet); Set result; - if (tmpSet.isEmpty()) { - result = Collections.emptySet(); - } else { - result = new HashSet<>(tmpSet); + int size = tmpSet.size(); + switch (size) { + case 0: { + result = Collections.emptySet(); + break; + } + case 1: { + String supCls = tmpSet.iterator().next(); + if (supCls.equals(Consts.CLASS_OBJECT)) { + result = OBJECT_SINGLE_SET; + } else { + result = Collections.singleton(supCls); + } + break; + } + default: { + result = new HashSet<>(tmpSet); + break; + } } map.put(cls.getName(), result); } diff --git a/jadx-core/src/main/resources/clst/core.jcst b/jadx-core/src/main/resources/clst/core.jcst index 732805fa3..906945f97 100644 Binary files a/jadx-core/src/main/resources/clst/core.jcst and b/jadx-core/src/main/resources/clst/core.jcst differ