fix: support 'swap' and 'wide' opcodes, other fixes for java-input
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user