fix: support 'swap' and 'wide' opcodes, other fixes for java-input
This commit is contained in:
@@ -6,10 +6,13 @@ dependencies {
|
||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.7'
|
||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
||||
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
testImplementation 'org.ow2.asm:asm:9.2'
|
||||
testImplementation 'org.ow2.asm:asm-util:9.2'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
|
||||
@@ -586,7 +586,6 @@ public class InsnGen {
|
||||
|
||||
case MOVE_MULTI:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-multi: ");
|
||||
int len = insn.getArgsCount();
|
||||
for (int i = 0; i < len - 1; i += 2) {
|
||||
addArg(code, insn.getArg(i));
|
||||
|
||||
@@ -147,6 +147,10 @@ public class AccessInfo {
|
||||
return (accFlags & AccessFlags.VOLATILE) != 0;
|
||||
}
|
||||
|
||||
public boolean isModuleInfo() {
|
||||
return (accFlags & AccessFlags.MODULE) != 0;
|
||||
}
|
||||
|
||||
public AFType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -200,6 +204,9 @@ public class AccessInfo {
|
||||
if ((accFlags & AccessFlags.STRICT) != 0) {
|
||||
code.append("strict ");
|
||||
}
|
||||
if (isModuleInfo()) {
|
||||
code.append("/* module-info */ ");
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
if ((accFlags & AccessFlags.SUPER) != 0) {
|
||||
code.append("/* super */ ");
|
||||
|
||||
@@ -20,6 +20,8 @@ import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
||||
@@ -27,6 +29,7 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultClassAttr;
|
||||
import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr;
|
||||
import jadx.api.plugins.input.data.attributes.types.InnerClsInfo;
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.api.plugins.input.data.impl.ListConsumer;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -97,34 +100,46 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
|
||||
private void initialLoad(IClassData cls) {
|
||||
try {
|
||||
String superType = cls.getSuperType();
|
||||
if (superType == null) {
|
||||
// only java.lang.Object don't have super class
|
||||
if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
|
||||
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
|
||||
}
|
||||
this.superClass = null;
|
||||
} else {
|
||||
this.superClass = ArgType.object(superType);
|
||||
}
|
||||
addAttrs(cls.getAttributes());
|
||||
this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
|
||||
this.superClass = checkSuperType(cls);
|
||||
this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
|
||||
|
||||
methods = new ArrayList<>();
|
||||
fields = new ArrayList<>();
|
||||
cls.visitFieldsAndMethods(
|
||||
fld -> fields.add(FieldNode.build(this, fld)),
|
||||
mth -> methods.add(MethodNode.build(this, mth)));
|
||||
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
||||
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
||||
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
|
||||
this.fields = fieldsConsumer.getResult();
|
||||
this.methods = methodsConsumer.getResult();
|
||||
|
||||
addAttrs(cls.getAttributes());
|
||||
accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
|
||||
initStaticValues(fields);
|
||||
processAttributes(this);
|
||||
buildCache();
|
||||
|
||||
// TODO: implement module attribute parsing
|
||||
if (this.accessFlags.isModuleInfo()) {
|
||||
this.addWarnComment("Modules not supported yet");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType checkSuperType(IClassData cls) {
|
||||
String superType = cls.getSuperType();
|
||||
if (superType == null) {
|
||||
if (clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
|
||||
// java.lang.Object don't have super class
|
||||
return null;
|
||||
}
|
||||
if (this.accessFlags.isModuleInfo()) {
|
||||
// module-info also don't have super class
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
|
||||
}
|
||||
return ArgType.object(superType);
|
||||
}
|
||||
|
||||
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
|
||||
this.superClass = superClass;
|
||||
this.interfaces = interfaces;
|
||||
|
||||
@@ -97,14 +97,7 @@ public class RootNode {
|
||||
}
|
||||
if (classes.size() != clsMap.size()) {
|
||||
// class name duplication detected
|
||||
classes.stream().collect(Collectors.groupingBy(ClassNode::getClassInfo))
|
||||
.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().size() > 1)
|
||||
.forEach(entry -> {
|
||||
LOG.warn("Found duplicated class: {}, count: {}. Only one will be loaded!", entry.getKey(),
|
||||
entry.getValue().size());
|
||||
entry.getValue().forEach(cls -> cls.addAttr(AType.COMMENTS, "WARNING: Classes with same name are omitted"));
|
||||
});
|
||||
markDuplicatedClasses(classes);
|
||||
}
|
||||
classes = new ArrayList<>(clsMap.values());
|
||||
// sort classes by name, expect top classes before inner
|
||||
@@ -135,6 +128,27 @@ public class RootNode {
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
}
|
||||
|
||||
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
||||
classes.stream()
|
||||
.collect(Collectors.groupingBy(ClassNode::getClassInfo))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().size() > 1)
|
||||
.forEach(entry -> {
|
||||
List<String> sources = Utils.collectionMap(entry.getValue(), ClassNode::getInputFileName);
|
||||
LOG.warn("Found duplicated class: {}, count: {}. Only one will be loaded!\n {}",
|
||||
entry.getKey(), entry.getValue().size(), String.join("\n ", sources));
|
||||
entry.getValue().forEach(cls -> {
|
||||
String thisSource = cls.getInputFileName();
|
||||
String otherSourceStr = sources.stream()
|
||||
.filter(s -> !s.equals(thisSource))
|
||||
.sorted()
|
||||
.collect(Collectors.joining("\n "));
|
||||
cls.addAttr(AType.COMMENTS, "WARNING: Classes with same name are omitted:\n " + otherSourceStr + '\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void addClassNode(ClassNode clsNode) {
|
||||
classes.add(clsNode);
|
||||
clsMap.put(clsNode.getClassInfo(), clsNode);
|
||||
|
||||
@@ -41,15 +41,15 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
return args;
|
||||
}
|
||||
|
||||
protected void decompile(JadxArgs jadxArgs) {
|
||||
decompile(jadxArgs, null, null);
|
||||
protected JadxDecompiler decompile(JadxArgs jadxArgs) {
|
||||
return decompile(jadxArgs, null, null);
|
||||
}
|
||||
|
||||
protected void decompile(JadxArgs jadxArgs, String clsPatternStr) {
|
||||
decompile(jadxArgs, clsPatternStr, null);
|
||||
protected JadxDecompiler decompile(JadxArgs jadxArgs, String clsPatternStr) {
|
||||
return decompile(jadxArgs, clsPatternStr, null);
|
||||
}
|
||||
|
||||
protected void decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
|
||||
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
|
||||
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
||||
jadx.getPluginManager().unload("java-convert");
|
||||
jadx.load();
|
||||
@@ -61,6 +61,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
processByPatterns(jadx, clsPatternStr, mthPatternStr);
|
||||
}
|
||||
printErrorReport(jadx);
|
||||
return jadx;
|
||||
}
|
||||
|
||||
private void processAll(JadxDecompiler jadx) {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.util.CheckClassAdapter;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestJavaSwap extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("StringBufferReplaceableByString")
|
||||
public static class TestCls {
|
||||
private Iterable<String> field;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String string = String.valueOf(this.field);
|
||||
return new StringBuilder(8 + String.valueOf(string).length())
|
||||
.append("concat(").append(string).append(")")
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJava() {
|
||||
useJavaInput();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
// TODO: find up-to-date assembler/disassembler in java
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
cw.visit(Opcodes.V1_8, 0, "TestCls", null, "java/lang/Object", new String[] {});
|
||||
cw.visitField(Opcodes.ACC_PRIVATE, "field", "Ljava/lang/Iterable;", null, null).visitEnd();
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, new String[] {});
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, "TestCls", "field", "Ljava/lang/Iterable;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 1);
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, 8);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
|
||||
mv.visitInsn(Opcodes.IADD);
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP_X1);
|
||||
mv.visitInsn(Opcodes.SWAP);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(I)V", false);
|
||||
mv.visitLdcInsn("concat(");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
|
||||
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
|
||||
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
||||
mv.visitLdcInsn(")");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
|
||||
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitMaxs(0, 0); // auto calculated
|
||||
mv.visitEnd();
|
||||
cw.visitEnd();
|
||||
byte[] clsBytes = cw.toByteArray();
|
||||
|
||||
StringWriter results = new StringWriter();
|
||||
CheckClassAdapter.verify(new ClassReader(clsBytes), false, new PrintWriter(results));
|
||||
assertThat(results.toString()).isEmpty();
|
||||
|
||||
Path clsFile = FileUtils.createTempFile(".class");
|
||||
Files.write(clsFile, clsBytes);
|
||||
List<File> files = Collections.singletonList(clsFile.toFile());
|
||||
|
||||
useJavaInput();
|
||||
assertThat(getClassNodeFromFiles(files, "TestCls"))
|
||||
.code();
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.ISeqConsumer;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
@@ -92,7 +93,7 @@ public class DexClassData implements IClassData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldsAndMethods(Consumer<IFieldData> fieldConsumer, Consumer<IMethodData> mthConsumer) {
|
||||
public void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldConsumer, ISeqConsumer<IMethodData> mthConsumer) {
|
||||
int classDataOff = getClassDataOff();
|
||||
if (classDataOff == 0) {
|
||||
return;
|
||||
@@ -103,6 +104,9 @@ public class DexClassData implements IClassData {
|
||||
int directMthCount = data.readUleb128();
|
||||
int virtualMthCount = data.readUleb128();
|
||||
|
||||
fieldConsumer.init(staticFieldsCount + instanceFieldsCount);
|
||||
mthConsumer.init(directMthCount + virtualMthCount);
|
||||
|
||||
annotationsParser.setOffset(getAnnotationsOff());
|
||||
visitFields(fieldConsumer, data, staticFieldsCount, instanceFieldsCount);
|
||||
visitMethods(mthConsumer, data, directMthCount, virtualMthCount);
|
||||
|
||||
+4
-34
@@ -1,9 +1,6 @@
|
||||
package jadx.plugins.input.javaconvert;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
@@ -20,6 +17,7 @@ import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
|
||||
public class JavaConvertLoader {
|
||||
@@ -81,7 +79,7 @@ public class JavaConvertLoader {
|
||||
try {
|
||||
String entryName = entry.getName();
|
||||
if (entryName.endsWith(".jar")) {
|
||||
Path tempJar = saveInputStreamToFile(in, ".jar");
|
||||
Path tempJar = CommonFileUtils.saveToTempFile(in, ".jar");
|
||||
result.addTempPath(tempJar);
|
||||
LOG.debug("Loading jar: {} ...", entryName);
|
||||
convertJar(result, tempJar);
|
||||
@@ -134,14 +132,14 @@ public class JavaConvertLoader {
|
||||
|| entryName.startsWith("META-INF/versions/")) {
|
||||
return;
|
||||
}
|
||||
byte[] clsFileContent = inputStreamToByteArray(in);
|
||||
byte[] clsFileContent = CommonFileUtils.loadBytes(in);
|
||||
String clsName = AsmUtils.getNameFromClassFile(clsFileContent);
|
||||
if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) {
|
||||
throw new IOException("Can't read class name from file: " + entryName);
|
||||
}
|
||||
addJarEntry(jo, clsName + ".class", clsFileContent, entry.getLastModifiedTime());
|
||||
} else if (entryName.endsWith(".jar")) {
|
||||
Path tempJar = saveInputStreamToFile(in, ".jar");
|
||||
Path tempJar = CommonFileUtils.saveToTempFile(in, ".jar");
|
||||
result.addTempPath(tempJar);
|
||||
convertJar(result, tempJar);
|
||||
}
|
||||
@@ -190,32 +188,4 @@ public class JavaConvertLoader {
|
||||
jar.write(content);
|
||||
jar.closeEntry();
|
||||
}
|
||||
|
||||
private static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[8 * 1024];
|
||||
while (true) {
|
||||
int count = input.read(buffer);
|
||||
if (count == -1) {
|
||||
break;
|
||||
}
|
||||
output.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] inputStreamToByteArray(InputStream input) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
copyStream(input, output);
|
||||
return output.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path saveInputStreamToFile(InputStream in, String suffix) throws IOException {
|
||||
Path tempJar = Files.createTempFile("jadx-temp-", suffix);
|
||||
try (OutputStream out = Files.newOutputStream(tempJar)) {
|
||||
copyStream(in, out);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to save temp file", e);
|
||||
}
|
||||
return tempJar;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-24
@@ -1,7 +1,6 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -13,9 +12,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
|
||||
public class JavaFileLoader {
|
||||
@@ -38,35 +39,53 @@ public class JavaFileLoader {
|
||||
|
||||
private List<JavaClassReader> loadFromFile(File file) {
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return loadReader(file, inputStream, file.getAbsolutePath());
|
||||
return loadReader(inputStream, file.getName(), file, null);
|
||||
} catch (Exception e) {
|
||||
LOG.error("File open error: {}", file.getAbsolutePath(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<JavaClassReader> loadReader(File file, InputStream in, String inputFileName) throws IOException {
|
||||
private List<JavaClassReader> loadReader(InputStream in, String name,
|
||||
@Nullable File file, @Nullable String parentFileName) throws IOException {
|
||||
byte[] magic = new byte[MAX_MAGIC_SIZE];
|
||||
if (in.read(magic) != magic.length) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (isStartWithBytes(magic, JAVA_CLASS_FILE_MAGIC)) {
|
||||
byte[] data = loadBytes(magic, in);
|
||||
JavaClassReader reader = new JavaClassReader(getNextUniqId(), inputFileName, data);
|
||||
byte[] data = CommonFileUtils.loadBytes(magic, in);
|
||||
String source = concatSource(parentFileName, name);
|
||||
JavaClassReader reader = new JavaClassReader(getNextUniqId(), source, data);
|
||||
return Collections.singletonList(reader);
|
||||
}
|
||||
if (file != null && isStartWithBytes(magic, ZIP_FILE_MAGIC)) {
|
||||
return collectFromZip(file);
|
||||
if (isStartWithBytes(magic, ZIP_FILE_MAGIC)) {
|
||||
if (file != null) {
|
||||
return collectFromZip(file, name);
|
||||
}
|
||||
File zipFile = CommonFileUtils.saveToTempFile(magic, in, ".zip").toFile();
|
||||
return collectFromZip(zipFile, concatSource(parentFileName, name));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<JavaClassReader> collectFromZip(File file) {
|
||||
private static String concatSource(@Nullable String parentFileName, String name) {
|
||||
if (parentFileName == null) {
|
||||
return name;
|
||||
}
|
||||
return parentFileName + ':' + name;
|
||||
}
|
||||
|
||||
private List<JavaClassReader> collectFromZip(File file, String name) {
|
||||
List<JavaClassReader> result = new ArrayList<>();
|
||||
try {
|
||||
ZipSecurity.readZipEntries(file, (entry, in) -> {
|
||||
try {
|
||||
result.addAll(loadReader(null, in, entry.getName()));
|
||||
String entryName = entry.getName();
|
||||
if (entryName.startsWith("META-INF/versions/")) {
|
||||
// skip classes for different java versions
|
||||
return;
|
||||
}
|
||||
result.addAll(loadReader(in, entryName, null, name));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to read zip entry: {}", entry, e);
|
||||
}
|
||||
@@ -90,21 +109,6 @@ public class JavaFileLoader {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static byte[] loadBytes(byte[] prefix, InputStream in) throws IOException {
|
||||
int estimateSize = prefix.length + in.available();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(estimateSize);
|
||||
out.write(prefix);
|
||||
byte[] buffer = new byte[8 * 1024];
|
||||
while (true) {
|
||||
int len = in.read(buffer);
|
||||
if (len == -1) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private int getNextUniqId() {
|
||||
return classUniqId++;
|
||||
}
|
||||
|
||||
+7
-4
@@ -239,14 +239,17 @@ public class ConstPoolReader {
|
||||
|
||||
private String fixType(String clsName) {
|
||||
switch (clsName.charAt(0)) {
|
||||
case 'L':
|
||||
case 'T':
|
||||
case '[':
|
||||
return clsName;
|
||||
|
||||
default:
|
||||
return 'L' + clsName + ';';
|
||||
case 'L':
|
||||
case 'T':
|
||||
if (clsName.endsWith(";")) {
|
||||
return clsName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 'L' + clsName + ';';
|
||||
}
|
||||
|
||||
private void jumpToData(int idx) {
|
||||
|
||||
+4
-2
@@ -3,7 +3,6 @@ package jadx.plugins.input.java.data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -11,6 +10,7 @@ import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.ISeqConsumer;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.JavaClassReader;
|
||||
@@ -74,11 +74,12 @@ public class JavaClassData implements IClassData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldsAndMethods(Consumer<IFieldData> fieldsConsumer, Consumer<IMethodData> mthConsumer) {
|
||||
public void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldsConsumer, ISeqConsumer<IMethodData> mthConsumer) {
|
||||
int clsIdx = data.absPos(offsets.getClsTypeOffset()).readU2();
|
||||
String classType = constPoolReader.getClass(clsIdx);
|
||||
DataReader reader = data.absPos(offsets.getFieldsOffset()).copy();
|
||||
int fieldsCount = reader.readU2();
|
||||
fieldsConsumer.init(fieldsCount);
|
||||
if (fieldsCount != 0) {
|
||||
JavaFieldData field = new JavaFieldData();
|
||||
field.setParentClassType(classType);
|
||||
@@ -89,6 +90,7 @@ public class JavaClassData implements IClassData {
|
||||
}
|
||||
|
||||
int methodsCount = reader.readU2();
|
||||
mthConsumer.init(methodsCount);
|
||||
if (methodsCount != 0) {
|
||||
JavaMethodRef methodRef = new JavaMethodRef();
|
||||
methodRef.setParentClassType(classType);
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ public class AttributesReader {
|
||||
|
||||
private final JavaClassData clsData;
|
||||
private final ConstPoolReader constPool;
|
||||
private final Map<Integer, JavaAttrType<?>> attrMap = new HashMap<>(JavaAttrType.size());
|
||||
private final Map<Integer, JavaAttrType<?>> attrCache = new HashMap<>(JavaAttrType.size());
|
||||
|
||||
public AttributesReader(JavaClassData clsData, ConstPoolReader constPoolReader) {
|
||||
this.clsData = clsData;
|
||||
@@ -95,8 +95,8 @@ public class AttributesReader {
|
||||
}
|
||||
|
||||
private JavaAttrType<?> resolveAttrReader(int nameIdx) {
|
||||
return attrMap.computeIfAbsent(nameIdx, idx -> {
|
||||
String attrName = constPool.getUtf8(nameIdx);
|
||||
return attrCache.computeIfAbsent(nameIdx, idx -> {
|
||||
String attrName = constPool.getUtf8(idx);
|
||||
JavaAttrType<?> attrType = JavaAttrType.byName(attrName);
|
||||
if (attrType == null) {
|
||||
LOG.warn("Unknown java class attribute type: {}", attrName);
|
||||
|
||||
+17
@@ -39,6 +39,8 @@ public final class JavaAttrType<T extends IJavaAttribute> {
|
||||
public static final JavaAttrType<JavaAnnotationsAttr> BUILD_ANNOTATIONS;
|
||||
public static final JavaAttrType<JavaParamAnnsAttr> RUNTIME_PARAMETER_ANNOTATIONS;
|
||||
public static final JavaAttrType<JavaParamAnnsAttr> BUILD_PARAMETER_ANNOTATIONS;
|
||||
public static final JavaAttrType<IgnoredAttr> RUNTIME_TYPE_ANNOTATIONS;
|
||||
public static final JavaAttrType<IgnoredAttr> BUILD_TYPE_ANNOTATIONS;
|
||||
public static final JavaAttrType<JavaAnnotationDefaultAttr> ANNOTATION_DEFAULT;
|
||||
|
||||
public static final JavaAttrType<JavaSourceFileAttr> SOURCE_FILE;
|
||||
@@ -46,7 +48,10 @@ public final class JavaAttrType<T extends IJavaAttribute> {
|
||||
public static final JavaAttrType<JavaExceptionsAttr> EXCEPTIONS;
|
||||
|
||||
public static final JavaAttrType<IgnoredAttr> DEPRECATED;
|
||||
public static final JavaAttrType<IgnoredAttr> SYNTHETIC;
|
||||
public static final JavaAttrType<IgnoredAttr> STACK_MAP_TABLE;
|
||||
public static final JavaAttrType<IgnoredAttr> ENCLOSING_METHOD;
|
||||
public static final JavaAttrType<IgnoredAttr> MODULE;
|
||||
|
||||
static {
|
||||
NAME_TO_TYPE_MAP = new HashMap<>();
|
||||
@@ -74,7 +79,14 @@ public final class JavaAttrType<T extends IJavaAttribute> {
|
||||
|
||||
// ignored
|
||||
DEPRECATED = bind("Deprecated", null); // duplicated by annotation
|
||||
SYNTHETIC = bind("Synthetic", null); // duplicated by access flag
|
||||
STACK_MAP_TABLE = bind("StackMapTable", null);
|
||||
ENCLOSING_METHOD = bind("EnclosingMethod", null);
|
||||
|
||||
// TODO: not supported yet
|
||||
RUNTIME_TYPE_ANNOTATIONS = bind("RuntimeVisibleTypeAnnotations", null);
|
||||
BUILD_TYPE_ANNOTATIONS = bind("RuntimeInvisibleTypeAnnotations", null);
|
||||
MODULE = bind("Module", null);
|
||||
}
|
||||
|
||||
private static <A extends IJavaAttribute> JavaAttrType<A> bind(String name, IJavaAttributeReader reader) {
|
||||
@@ -113,4 +125,9 @@ public final class JavaAttrType<T extends IJavaAttribute> {
|
||||
public IJavaAttributeReader getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -52,8 +52,7 @@ public class JavaCodeReader implements ICodeReader {
|
||||
@Override
|
||||
public void visitInstructions(Consumer<InsnData> insnConsumer) {
|
||||
Set<Integer> excHandlers = getExcHandlers();
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
int maxStack = readMaxStack();
|
||||
reader.skip(2);
|
||||
int codeSize = reader.readU4();
|
||||
|
||||
@@ -96,16 +95,20 @@ public class JavaCodeReader implements ICodeReader {
|
||||
|
||||
@Override
|
||||
public int getRegistersCount() {
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
int maxStack = readMaxStack();
|
||||
int maxLocals = reader.readU2();
|
||||
return maxStack + maxLocals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArgsStartReg() {
|
||||
return readMaxStack();
|
||||
}
|
||||
|
||||
private int readMaxStack() {
|
||||
reader.absPos(codeOffset);
|
||||
return reader.readU2(); // maxStack
|
||||
int maxStack = reader.readU2();
|
||||
return maxStack + 1; // add one temporary register (for `swap` opcode)
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,9 +117,9 @@ public class JavaCodeReader implements ICodeReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IDebugInfo getDebugInfo() {
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
@Nullable
|
||||
public IDebugInfo getDebugInfo() {
|
||||
int maxStack = readMaxStack();
|
||||
reader.skip(2);
|
||||
reader.skip(reader.readU4());
|
||||
reader.skip(reader.readU2() * 8);
|
||||
|
||||
+6
-1
@@ -10,6 +10,7 @@ import jadx.plugins.input.java.data.code.decoders.InvokeDecoder;
|
||||
import jadx.plugins.input.java.data.code.decoders.LoadConstDecoder;
|
||||
import jadx.plugins.input.java.data.code.decoders.LookupSwitchDecoder;
|
||||
import jadx.plugins.input.java.data.code.decoders.TableSwitchDecoder;
|
||||
import jadx.plugins.input.java.data.code.decoders.WideDecoder;
|
||||
|
||||
import static jadx.plugins.input.java.data.code.StackState.SVType.NARROW;
|
||||
import static jadx.plugins.input.java.data.code.StackState.SVType.WIDE;
|
||||
@@ -172,6 +173,10 @@ public class JavaInsnsRegister {
|
||||
.peekFrom(2, 4).peekFrom(0, 5);
|
||||
}
|
||||
});
|
||||
register(arr, 0x5f, "swap", 0, 6, Opcode.MOVE_MULTI,
|
||||
s -> s.peekFrom(-1, 0).peekFrom(1, 1)
|
||||
.peekFrom(1, 2).peekFrom(0, 3)
|
||||
.peekFrom(0, 4).peekFrom(-1, 5));
|
||||
|
||||
register(arr, 0x60, "iadd", 0, 3, Opcode.ADD_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x61, "ladd", 0, 3, Opcode.ADD_LONG, twoRegsWithResult(WIDE));
|
||||
@@ -295,7 +300,7 @@ public class JavaInsnsRegister {
|
||||
register(arr, 0xc2, "monitorenter", 0, 1, Opcode.MONITOR_ENTER, s -> s.pop(0));
|
||||
register(arr, 0xc3, "monitorexit", 0, 1, Opcode.MONITOR_EXIT, s -> s.pop(0));
|
||||
|
||||
// register(arr, 0xc4, "wide", 0, 1, Opcode.NOP, s -> s.pop(0));
|
||||
register(arr, 0xc4, "wide", -1, -1, Opcode.NOP, new WideDecoder());
|
||||
|
||||
register(arr, 0xc5, "multianewarray", 3, -1, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, newArrayMulti());
|
||||
register(arr, 0xc6, "ifnull", 2, 1, Opcode.IF_EQZ, zeroCmp());
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.code.CodeDecodeState;
|
||||
import jadx.plugins.input.java.data.code.JavaInsnData;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class WideDecoder implements IJavaInsnDecoder {
|
||||
private static final int IINC = 0x84;
|
||||
|
||||
@Override
|
||||
public void decode(CodeDecodeState state) {
|
||||
DataReader reader = state.reader();
|
||||
JavaInsnData insn = state.insn();
|
||||
int opcode = reader.readU1();
|
||||
if (opcode == IINC) {
|
||||
int varNum = reader.readU2();
|
||||
int constValue = reader.readS2();
|
||||
state.local(0, varNum).local(1, varNum).lit(constValue);
|
||||
insn.setPayloadSize(5);
|
||||
insn.setRegsCount(2);
|
||||
insn.setOpcode(Opcode.ADD_INT_LIT);
|
||||
return;
|
||||
}
|
||||
int index = reader.readU2();
|
||||
switch (opcode) {
|
||||
case 0x15: // iload,
|
||||
case 0x17: // fload
|
||||
case 0x19: // aload
|
||||
state.local(1, index).push(0);
|
||||
break;
|
||||
|
||||
case 0x16: // lload
|
||||
case 0x18: // dload
|
||||
state.local(1, index).pushWide(0);
|
||||
break;
|
||||
|
||||
case 0x36:
|
||||
case 0x37:
|
||||
case 0x38:
|
||||
case 0x39:
|
||||
case 0x3a:
|
||||
// *store
|
||||
state.pop(1).local(0, index);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Unexpected opcode in 'wide': 0x" + Integer.toHexString(opcode));
|
||||
}
|
||||
insn.setPayloadSize(3);
|
||||
insn.setRegsCount(2);
|
||||
insn.setOpcode(Opcode.MOVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(CodeDecodeState state) {
|
||||
DataReader reader = state.reader();
|
||||
JavaInsnData insn = state.insn();
|
||||
int opcode = reader.readU1();
|
||||
if (opcode == IINC) {
|
||||
reader.skip(4);
|
||||
insn.setPayloadSize(5);
|
||||
} else {
|
||||
reader.skip(2);
|
||||
insn.setPayloadSize(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package jadx.api.plugins.input.data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -21,7 +20,7 @@ public interface IClassData {
|
||||
|
||||
List<String> getInterfacesTypes();
|
||||
|
||||
void visitFieldsAndMethods(Consumer<IFieldData> fieldsConsumer, Consumer<IMethodData> mthConsumer);
|
||||
void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldsConsumer, ISeqConsumer<IMethodData> mthConsumer);
|
||||
|
||||
List<IJadxAttribute> getAttributes();
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package jadx.api.plugins.input.data;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* "Sequence consumer" allows getting count of elements available
|
||||
*/
|
||||
public interface ISeqConsumer<T> extends Consumer<T> {
|
||||
|
||||
default void init(int count) {
|
||||
// no-op implementation
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package jadx.api.plugins.input.data.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.api.plugins.input.data.ISeqConsumer;
|
||||
|
||||
public class ListConsumer<T, R> implements ISeqConsumer<T> {
|
||||
private final Function<T, R> convert;
|
||||
private List<R> list;
|
||||
|
||||
public ListConsumer(Function<T, R> convert) {
|
||||
this.convert = convert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(int count) {
|
||||
list = count == 0 ? Collections.emptyList() : new ArrayList<>(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(T t) {
|
||||
list.add(convert.apply(t));
|
||||
}
|
||||
|
||||
public List<R> getResult() {
|
||||
if (list == null) {
|
||||
// init not called
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package jadx.api.plugins.utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class CommonFileUtils {
|
||||
|
||||
public static Path saveToTempFile(InputStream in, String suffix) throws IOException {
|
||||
return saveToTempFile(null, in, suffix);
|
||||
}
|
||||
|
||||
public static Path saveToTempFile(byte[] dataPrefix, InputStream in, String suffix) throws IOException {
|
||||
Path tempFile = Files.createTempFile("jadx-temp-", suffix);
|
||||
try (OutputStream out = Files.newOutputStream(tempFile)) {
|
||||
if (dataPrefix != null) {
|
||||
out.write(dataPrefix);
|
||||
}
|
||||
copyStream(in, out);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to save temp file", e);
|
||||
}
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public static byte[] loadBytes(InputStream input) throws IOException {
|
||||
return loadBytes(null, input);
|
||||
}
|
||||
|
||||
public static byte[] loadBytes(byte[] dataPrefix, InputStream in) throws IOException {
|
||||
int estimateSize = dataPrefix == null ? in.available() : dataPrefix.length + in.available();
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream(estimateSize)) {
|
||||
if (dataPrefix != null) {
|
||||
out.write(dataPrefix);
|
||||
}
|
||||
copyStream(in, out);
|
||||
return out.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to read input stream to bytes array", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[8 * 1024];
|
||||
while (true) {
|
||||
int count = input.read(buffer);
|
||||
if (count == -1) {
|
||||
break;
|
||||
}
|
||||
output.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user