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