fix: support 'swap' and 'wide' opcodes, other fixes for java-input

This commit is contained in:
Skylot
2021-08-20 20:59:30 +03:00
parent 868fa90097
commit 9ea3f0f240
21 changed files with 425 additions and 111 deletions
@@ -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();
}
}