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
@@ -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++;
}
@@ -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) {
@@ -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);
@@ -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);
@@ -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;
}
}
@@ -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);
@@ -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());
@@ -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);
}
}
}