feat: input plugin for java bytecode
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
|
||||
public class JavaClassReader {
|
||||
private final int id;
|
||||
private final String fileName;
|
||||
private final byte[] data;
|
||||
|
||||
public JavaClassReader(int id, String fileName, byte[] data) {
|
||||
this.id = id;
|
||||
this.fileName = fileName;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public IClassData loadClassData() {
|
||||
return new JavaClassData(this);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
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;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
|
||||
public class JavaFileLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaFileLoader.class);
|
||||
|
||||
public static final int MAX_MAGIC_SIZE = 4;
|
||||
public static final byte[] JAVA_CLASS_FILE_MAGIC = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
|
||||
public static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 };
|
||||
|
||||
private static int classUniqId = 1;
|
||||
|
||||
public static List<JavaClassReader> collectFiles(List<Path> inputFiles) {
|
||||
return inputFiles.stream()
|
||||
.map(Path::toFile)
|
||||
.map(JavaFileLoader::loadFromFile)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<JavaClassReader> loadFromFile(File file) {
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return loadReader(file, inputStream, file.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
LOG.error("File open error: {}", file.getAbsolutePath(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<JavaClassReader> loadReader(File file, InputStream in, String inputFileName) 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);
|
||||
return Collections.singletonList(reader);
|
||||
}
|
||||
if (file != null && isStartWithBytes(magic, ZIP_FILE_MAGIC)) {
|
||||
return collectFromZip(file);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static List<JavaClassReader> collectFromZip(File file) {
|
||||
List<JavaClassReader> result = new ArrayList<>();
|
||||
try {
|
||||
ZipSecurity.readZipEntries(file, (entry, in) -> {
|
||||
try {
|
||||
result.addAll(loadReader(null, in, entry.getName()));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to read zip entry: {}", entry, e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to process zip file: {}", file.getAbsolutePath(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isStartWithBytes(byte[] fileMagic, byte[] expectedBytes) {
|
||||
int len = expectedBytes.length;
|
||||
if (fileMagic.length < len) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (fileMagic[i] != expectedBytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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[0xFFFF];
|
||||
while (true) {
|
||||
int len = in.read(buffer);
|
||||
if (len == -1) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static int getNextUniqId() {
|
||||
classUniqId++;
|
||||
if (classUniqId >= 0xFFFF) {
|
||||
resetDexUniqId();
|
||||
}
|
||||
return classUniqId;
|
||||
}
|
||||
|
||||
public static void resetDexUniqId() {
|
||||
classUniqId = 1;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.impl.EmptyLoadResult;
|
||||
|
||||
public class JavaInputPlugin implements JadxInputPlugin {
|
||||
|
||||
public static final JadxPluginInfo PLUGIN_INFO = new JadxPluginInfo(
|
||||
"java-input",
|
||||
"JavaInput",
|
||||
"Load .class and .jar files");
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
return PLUGIN_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> inputFiles) {
|
||||
List<JavaClassReader> readers = JavaFileLoader.collectFiles(inputFiles);
|
||||
if (readers.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return new JavaLoadResult(readers);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package jadx.plugins.input.java;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.IResourceData;
|
||||
|
||||
public class JavaLoadResult implements ILoadResult {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class);
|
||||
|
||||
private final List<JavaClassReader> readers;
|
||||
|
||||
public JavaLoadResult(List<JavaClassReader> readers) {
|
||||
this.readers = readers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClasses(Consumer<IClassData> consumer) {
|
||||
for (JavaClassReader reader : readers) {
|
||||
try {
|
||||
consumer.accept(reader.loadClassData());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to load class data for file: " + reader.getFileName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitResources(Consumer<IResourceData> consumer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return readers.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
readers.clear();
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
public class ClassOffsets {
|
||||
|
||||
private final int[] constPoolOffsets;
|
||||
private final int constPoolEnd;
|
||||
private final int interfacesEnd;
|
||||
private final int attributesOffset;
|
||||
|
||||
public ClassOffsets(DataReader data) {
|
||||
this.constPoolOffsets = readConstPool(data);
|
||||
this.constPoolEnd = data.getOffset();
|
||||
int interfacesCount = data.absPos(constPoolEnd + 6).readU2();
|
||||
data.skip(interfacesCount * 2);
|
||||
this.interfacesEnd = data.getOffset();
|
||||
skipFields(data);
|
||||
skipMethods(data);
|
||||
this.attributesOffset = data.getOffset();
|
||||
}
|
||||
|
||||
private static int[] readConstPool(DataReader data) {
|
||||
int cpSize = data.absPos(8).readU2();
|
||||
int[] cpOffsets = new int[cpSize + 1];
|
||||
for (int i = 1; i < cpSize; i++) {
|
||||
int tag = data.readU1();
|
||||
cpOffsets[i] = data.getOffset();
|
||||
ConstantType constType = ConstantType.getTypeByTag(tag);
|
||||
switch (constType) {
|
||||
case UTF8:
|
||||
data.skip(data.readU2());
|
||||
break;
|
||||
|
||||
case LONG:
|
||||
case DOUBLE:
|
||||
data.skip(8);
|
||||
i++;
|
||||
break;
|
||||
|
||||
default:
|
||||
data.skip(constType.getDataSize());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cpOffsets;
|
||||
}
|
||||
|
||||
private void skipFields(DataReader data) {
|
||||
int fieldsCount = data.readU2();
|
||||
for (int i = 0; i < fieldsCount; i++) {
|
||||
data.skip(6);
|
||||
skipAttributes(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void skipMethods(DataReader data) {
|
||||
int methodsCount = data.readU2();
|
||||
for (int i = 0; i < methodsCount; i++) {
|
||||
data.skip(6);
|
||||
skipAttributes(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void skipAttributes(DataReader data) {
|
||||
int attrCount = data.readU2();
|
||||
for (int i = 0; i < attrCount; i++) {
|
||||
data.skip(2);
|
||||
int len = data.readU4();
|
||||
data.skip(len);
|
||||
}
|
||||
}
|
||||
|
||||
public int getOffsetOfConstEntry(int num) {
|
||||
return constPoolOffsets[num];
|
||||
}
|
||||
|
||||
public int getAccessFlagsOffset() {
|
||||
return constPoolEnd;
|
||||
}
|
||||
|
||||
public int getClsTypeOffset() {
|
||||
return constPoolEnd + 2;
|
||||
}
|
||||
|
||||
public int getSuperTypeOffset() {
|
||||
return constPoolEnd + 4;
|
||||
}
|
||||
|
||||
public int getInterfacesOffset() {
|
||||
return constPoolEnd + 6;
|
||||
}
|
||||
|
||||
public int getFieldsOffset() {
|
||||
return interfacesEnd;
|
||||
}
|
||||
|
||||
public int getAttributesOffset() {
|
||||
return attributesOffset;
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.ICallSite;
|
||||
import jadx.api.plugins.input.data.IFieldRef;
|
||||
import jadx.api.plugins.input.data.IMethodHandle;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.impl.CallSite;
|
||||
import jadx.api.plugins.input.data.impl.FieldRefHandle;
|
||||
import jadx.api.plugins.input.data.impl.MethodRefHandle;
|
||||
import jadx.plugins.input.java.JavaClassReader;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaBootstrapMethodsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.data.RawBootstrapMethod;
|
||||
import jadx.plugins.input.java.utils.DescriptorParser;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class ConstPoolReader {
|
||||
private final JavaClassReader clsReader;
|
||||
private final JavaClassData clsData;
|
||||
private final DataReader data;
|
||||
private final ClassOffsets offsets;
|
||||
|
||||
public ConstPoolReader(JavaClassReader clsReader, JavaClassData javaClassData, DataReader data, ClassOffsets offsets) {
|
||||
this.clsReader = clsReader;
|
||||
this.clsData = javaClassData;
|
||||
this.data = data;
|
||||
this.offsets = offsets;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getClass(int idx) {
|
||||
jumpToData(idx);
|
||||
int nameIdx = data.readU2();
|
||||
return fixType(getUtf8(nameIdx));
|
||||
}
|
||||
|
||||
public IFieldRef getFieldRef(int idx) {
|
||||
jumpToData(idx);
|
||||
int clsIdx = data.readU2();
|
||||
int nameTypeIdx = data.readU2();
|
||||
jumpToData(nameTypeIdx);
|
||||
int nameIdx = data.readU2();
|
||||
int typeIdx = data.readU2();
|
||||
|
||||
JavaFieldData fieldData = new JavaFieldData();
|
||||
fieldData.setParentClassType(getClass(clsIdx));
|
||||
fieldData.setName(getUtf8(nameIdx));
|
||||
fieldData.setType(getUtf8(typeIdx));
|
||||
return fieldData;
|
||||
}
|
||||
|
||||
public String getFieldType(int idx) {
|
||||
jumpToData(idx);
|
||||
data.skip(2);
|
||||
int nameTypeIdx = data.readU2();
|
||||
jumpToData(nameTypeIdx);
|
||||
data.skip(2);
|
||||
int typeIdx = data.readU2();
|
||||
return getUtf8(typeIdx);
|
||||
}
|
||||
|
||||
public IMethodRef getMethodRef(int idx) {
|
||||
jumpToData(idx);
|
||||
int clsIdx = data.readU2();
|
||||
int nameTypeIdx = data.readU2();
|
||||
jumpToData(nameTypeIdx);
|
||||
int nameIdx = data.readU2();
|
||||
int descIdx = data.readU2();
|
||||
|
||||
JavaMethodRef mthRef = new JavaMethodRef();
|
||||
mthRef.initUniqId(clsReader, clsIdx, nameIdx, descIdx);
|
||||
mthRef.setParentClassType(getClass(clsIdx));
|
||||
mthRef.setName(getUtf8(nameIdx));
|
||||
mthRef.setDescr(getUtf8(descIdx));
|
||||
return mthRef;
|
||||
}
|
||||
|
||||
public ICallSite getCallSite(int idx) {
|
||||
ConstantType constType = jumpToConst(idx);
|
||||
switch (constType) {
|
||||
case INVOKE_DYNAMIC:
|
||||
int bootstrapMthIdx = data.readU2();
|
||||
int nameAndTypeIdx = data.readU2();
|
||||
jumpToData(nameAndTypeIdx);
|
||||
int nameIdx = data.readU2();
|
||||
int descIdx = data.readU2();
|
||||
return resolveMethodCallSite(bootstrapMthIdx, nameIdx, descIdx);
|
||||
case DYNAMIC:
|
||||
throw new JavaClassParseException("Field call site not yet implemented");
|
||||
default:
|
||||
throw new JavaClassParseException("Unexpected tag type for call site: " + constType);
|
||||
}
|
||||
}
|
||||
|
||||
private CallSite resolveMethodCallSite(int bootstrapMthIdx, int nameIdx, int descIdx) {
|
||||
JavaBootstrapMethodsAttr bootstrapMethodsAttr = clsData.loadAttribute(data, JavaAttrType.BOOTSTRAP_METHODS);
|
||||
if (bootstrapMethodsAttr == null) {
|
||||
throw new JavaClassParseException("Unexpected missing BootstrapMethods attribute");
|
||||
}
|
||||
RawBootstrapMethod rawBootstrapMethod = bootstrapMethodsAttr.getList().get(bootstrapMthIdx);
|
||||
|
||||
List<EncodedValue> values = new ArrayList<>(6);
|
||||
values.add(new EncodedValue(EncodedType.ENCODED_METHOD_HANDLE, getMethodHandle(rawBootstrapMethod.getMethodHandleIdx())));
|
||||
values.add(new EncodedValue(EncodedType.ENCODED_STRING, getUtf8(nameIdx)));
|
||||
values.add(new EncodedValue(EncodedType.ENCODED_METHOD_TYPE, DescriptorParser.parseToMethodProto(getUtf8(descIdx))));
|
||||
for (int argConstIdx : rawBootstrapMethod.getArgs()) {
|
||||
values.add(readAsEncodedValue(argConstIdx));
|
||||
}
|
||||
return new CallSite(values);
|
||||
}
|
||||
|
||||
private IMethodHandle getMethodHandle(int idx) {
|
||||
jumpToData(idx);
|
||||
int kind = data.readU1();
|
||||
int refIdx = data.readU2();
|
||||
MethodHandleType handleType = convertMethodHandleKind(kind);
|
||||
if (handleType.isField()) {
|
||||
return new FieldRefHandle(handleType, getFieldRef(refIdx));
|
||||
}
|
||||
return new MethodRefHandle(handleType, getMethodRef(refIdx));
|
||||
}
|
||||
|
||||
private MethodHandleType convertMethodHandleKind(int kind) {
|
||||
switch (kind) {
|
||||
case 1:
|
||||
return MethodHandleType.STATIC_PUT;
|
||||
case 2:
|
||||
return MethodHandleType.STATIC_GET;
|
||||
case 3:
|
||||
return MethodHandleType.INSTANCE_PUT;
|
||||
case 4:
|
||||
return MethodHandleType.INSTANCE_GET;
|
||||
case 5:
|
||||
return MethodHandleType.INVOKE_INSTANCE;
|
||||
case 6:
|
||||
return MethodHandleType.INVOKE_STATIC;
|
||||
case 7:
|
||||
return MethodHandleType.INVOKE_DIRECT;
|
||||
case 8:
|
||||
return MethodHandleType.INVOKE_CONSTRUCTOR;
|
||||
case 9:
|
||||
return MethodHandleType.INVOKE_INTERFACE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown method handle type: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUtf8(int idx) {
|
||||
if (idx == 0) {
|
||||
return null;
|
||||
}
|
||||
jumpToData(idx);
|
||||
return readString();
|
||||
}
|
||||
|
||||
public ConstantType jumpToConst(int idx) {
|
||||
jumpToTag(idx);
|
||||
return ConstantType.getTypeByTag(data.readU1());
|
||||
}
|
||||
|
||||
public String readString() {
|
||||
int len = data.readU2();
|
||||
byte[] bytes = data.readBytes(len);
|
||||
return parseString(bytes);
|
||||
}
|
||||
|
||||
public int readU2() {
|
||||
return data.readU2();
|
||||
}
|
||||
|
||||
public int readU4() {
|
||||
return data.readU4();
|
||||
}
|
||||
|
||||
public long readU8() {
|
||||
return data.readU8();
|
||||
}
|
||||
|
||||
public int getInt(int idx) {
|
||||
jumpToData(idx);
|
||||
return data.readS4();
|
||||
}
|
||||
|
||||
public long getLong(int idx) {
|
||||
jumpToData(idx);
|
||||
return data.readS8();
|
||||
}
|
||||
|
||||
public double getDouble(int idx) {
|
||||
jumpToData(idx);
|
||||
return Double.longBitsToDouble(data.readU8());
|
||||
}
|
||||
|
||||
public float getFloat(int idx) {
|
||||
jumpToData(idx);
|
||||
return Float.intBitsToFloat(data.readU4());
|
||||
}
|
||||
|
||||
public EncodedValue readAsEncodedValue(int idx) {
|
||||
ConstantType constantType = jumpToConst(idx);
|
||||
switch (constantType) {
|
||||
case UTF8:
|
||||
return new EncodedValue(EncodedType.ENCODED_STRING, readString());
|
||||
case STRING:
|
||||
return new EncodedValue(EncodedType.ENCODED_STRING, getUtf8(readU2()));
|
||||
case INTEGER:
|
||||
return new EncodedValue(EncodedType.ENCODED_INT, data.readS4());
|
||||
case FLOAT:
|
||||
return new EncodedValue(EncodedType.ENCODED_FLOAT, Float.intBitsToFloat(data.readU4()));
|
||||
case LONG:
|
||||
return new EncodedValue(EncodedType.ENCODED_LONG, data.readS8());
|
||||
case DOUBLE:
|
||||
return new EncodedValue(EncodedType.ENCODED_DOUBLE, Double.longBitsToDouble(data.readU8()));
|
||||
case METHOD_TYPE:
|
||||
return new EncodedValue(EncodedType.ENCODED_METHOD_TYPE, DescriptorParser.parseToMethodProto(getUtf8(readU2())));
|
||||
case METHOD_HANDLE:
|
||||
return new EncodedValue(EncodedType.ENCODED_METHOD_HANDLE, getMethodHandle(idx));
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Can't encode constant " + constantType + " as encoded value");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String parseString(byte[] bytes) {
|
||||
// TODO: parse modified UTF-8
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String fixType(String clsName) {
|
||||
switch (clsName.charAt(0)) {
|
||||
case 'L':
|
||||
case 'T':
|
||||
case '[':
|
||||
return clsName;
|
||||
|
||||
default:
|
||||
return 'L' + clsName + ';';
|
||||
}
|
||||
}
|
||||
|
||||
private void jumpToData(int idx) {
|
||||
data.absPos(offsets.getOffsetOfConstEntry(idx));
|
||||
}
|
||||
|
||||
private void jumpToTag(int idx) {
|
||||
data.absPos(offsets.getOffsetOfConstEntry(idx) - 1);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public enum ConstantType {
|
||||
UTF8(1, -1),
|
||||
INTEGER(3, 4),
|
||||
FLOAT(4, 4),
|
||||
LONG(5, 8),
|
||||
DOUBLE(6, 8),
|
||||
CLASS(7, 2),
|
||||
STRING(8, 2),
|
||||
FIELD_REF(9, 4),
|
||||
METHOD_REF(10, 4),
|
||||
INTERFACE_METHOD_REF(11, 4),
|
||||
NAME_AND_TYPE(12, 4),
|
||||
METHOD_HANDLE(15, 3),
|
||||
METHOD_TYPE(16, 2),
|
||||
DYNAMIC(17, 4),
|
||||
INVOKE_DYNAMIC(18, 4),
|
||||
MODULE(19, 2),
|
||||
PACKAGE(20, 2);
|
||||
|
||||
private static final ConstantType[] TAG_MAP;
|
||||
|
||||
static {
|
||||
ConstantType[] values = ConstantType.values();
|
||||
int maxVal = Stream.of(values)
|
||||
.mapToInt(ConstantType::getTag)
|
||||
.max()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Empty ConstantType enum"));
|
||||
|
||||
ConstantType[] map = new ConstantType[maxVal + 1];
|
||||
for (ConstantType value : values) {
|
||||
map[value.getTag()] = value;
|
||||
}
|
||||
TAG_MAP = map;
|
||||
}
|
||||
|
||||
public static ConstantType getTypeByTag(int tag) {
|
||||
ConstantType type = TAG_MAP[tag];
|
||||
if (type == null) {
|
||||
throw new JavaClassParseException("Unknown constant pool tag: " + tag);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private final byte tag;
|
||||
private final int dataSize;
|
||||
|
||||
ConstantType(int tag, int dataSize) {
|
||||
this.tag = (byte) tag;
|
||||
this.dataSize = dataSize;
|
||||
}
|
||||
|
||||
public byte getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public int getDataSize() {
|
||||
return dataSize;
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DataReader {
|
||||
private final byte[] data;
|
||||
private int offset;
|
||||
|
||||
public DataReader(byte[] data) {
|
||||
this(data, 0);
|
||||
}
|
||||
|
||||
public DataReader(byte[] data, int offset) {
|
||||
this.data = data;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public DataReader copy() {
|
||||
return new DataReader(data, offset);
|
||||
}
|
||||
|
||||
public DataReader absPos(int offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void skip(int size) {
|
||||
this.offset += size;
|
||||
}
|
||||
|
||||
public int readS1() {
|
||||
int pos = this.offset;
|
||||
byte b1 = this.data[pos];
|
||||
this.offset = pos + 1;
|
||||
return b1;
|
||||
}
|
||||
|
||||
public int readU1() {
|
||||
int pos = this.offset;
|
||||
byte b1 = this.data[pos];
|
||||
this.offset = pos + 1;
|
||||
return b1 & 0xFF;
|
||||
}
|
||||
|
||||
public int readS2() {
|
||||
int pos = this.offset;
|
||||
byte[] d = this.data;
|
||||
byte b1 = d[pos++];
|
||||
byte b2 = d[pos++];
|
||||
this.offset = pos;
|
||||
return b1 << 8 | (b2 & 0xFF);
|
||||
}
|
||||
|
||||
public int readU2() {
|
||||
int pos = this.offset;
|
||||
byte[] d = this.data;
|
||||
byte b1 = d[pos++];
|
||||
byte b2 = d[pos++];
|
||||
this.offset = pos;
|
||||
return (b1 & 0xFF) << 8 | (b2 & 0xFF);
|
||||
}
|
||||
|
||||
public int readS4() {
|
||||
int pos = this.offset;
|
||||
byte[] d = this.data;
|
||||
byte b1 = d[pos++];
|
||||
byte b2 = d[pos++];
|
||||
byte b3 = d[pos++];
|
||||
byte b4 = d[pos++];
|
||||
this.offset = pos;
|
||||
return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF);
|
||||
}
|
||||
|
||||
public int readU4() {
|
||||
int pos = this.offset;
|
||||
byte[] d = this.data;
|
||||
byte b1 = d[pos++];
|
||||
byte b2 = d[pos++];
|
||||
byte b3 = d[pos++];
|
||||
byte b4 = d[pos++];
|
||||
this.offset = pos;
|
||||
return (b1 & 0xFF) << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF);
|
||||
}
|
||||
|
||||
public long readS8() {
|
||||
long high = readS4();
|
||||
long low = readU4() & 0xFFFF_FFFFL;
|
||||
return high << 32 | low;
|
||||
}
|
||||
|
||||
public long readU8() {
|
||||
long high = readU4() & 0xFFFF_FFFFL;
|
||||
long low = readU4() & 0xFFFF_FFFFL;
|
||||
return high << 32 | low;
|
||||
}
|
||||
|
||||
public byte[] readBytes(int len) {
|
||||
int pos = this.offset;
|
||||
this.offset = pos + len;
|
||||
return Arrays.copyOfRange(data, pos, pos + len);
|
||||
}
|
||||
|
||||
public List<String> readClassesList(ConstPoolReader constPool) {
|
||||
int len = readU2();
|
||||
if (len == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
list.add(constPool.getClass(readU2()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
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;
|
||||
|
||||
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.attributes.IJadxAttribute;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.JavaClassReader;
|
||||
import jadx.plugins.input.java.data.attributes.AttributesReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr;
|
||||
import jadx.plugins.input.java.utils.DisasmUtils;
|
||||
|
||||
public class JavaClassData implements IClassData {
|
||||
private final JavaClassReader clsReader;
|
||||
private final DataReader data;
|
||||
private final ClassOffsets offsets;
|
||||
private final ConstPoolReader constPoolReader;
|
||||
private final AttributesReader attributesReader;
|
||||
|
||||
public JavaClassData(JavaClassReader clsReader) {
|
||||
this.clsReader = clsReader;
|
||||
this.data = new DataReader(clsReader.getData());
|
||||
this.offsets = new ClassOffsets(this.data);
|
||||
this.constPoolReader = new ConstPoolReader(clsReader, this, this.data.copy(), this.offsets);
|
||||
this.attributesReader = new AttributesReader(this, this.constPoolReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IClassData copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccessFlags() {
|
||||
return data.absPos(offsets.getAccessFlagsOffset()).readU2();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
int idx = data.absPos(offsets.getClsTypeOffset()).readU2();
|
||||
return constPoolReader.getClass(idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getSuperType() {
|
||||
int idx = data.absPos(offsets.getSuperTypeOffset()).readU2();
|
||||
if (idx == 0) {
|
||||
return null;
|
||||
}
|
||||
return constPoolReader.getClass(idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getInterfacesTypes() {
|
||||
data.absPos(offsets.getInterfacesOffset());
|
||||
return data.readClassesList(constPoolReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputFileName() {
|
||||
return this.clsReader.getFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldsAndMethods(Consumer<IFieldData> fieldsConsumer, Consumer<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();
|
||||
if (fieldsCount != 0) {
|
||||
JavaFieldData field = new JavaFieldData();
|
||||
field.setParentClassType(classType);
|
||||
for (int i = 0; i < fieldsCount; i++) {
|
||||
parseField(reader, field);
|
||||
fieldsConsumer.accept(field);
|
||||
}
|
||||
}
|
||||
|
||||
int methodsCount = reader.readU2();
|
||||
if (methodsCount != 0) {
|
||||
JavaMethodRef methodRef = new JavaMethodRef();
|
||||
methodRef.setParentClassType(classType);
|
||||
JavaMethodData method = new JavaMethodData(this, methodRef);
|
||||
for (int i = 0; i < methodsCount; i++) {
|
||||
parseMethod(reader, method, clsIdx);
|
||||
mthConsumer.accept(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseField(DataReader reader, JavaFieldData field) {
|
||||
int accessFlags = reader.readU2();
|
||||
int nameIdx = reader.readU2();
|
||||
int typeIdx = reader.readU2();
|
||||
JavaAttrStorage attributes = attributesReader.load(reader);
|
||||
|
||||
field.setAccessFlags(accessFlags);
|
||||
field.setName(constPoolReader.getUtf8(nameIdx));
|
||||
field.setType(constPoolReader.getUtf8(typeIdx));
|
||||
field.setAttributes(attributes);
|
||||
}
|
||||
|
||||
private void parseMethod(DataReader reader, JavaMethodData method, int clsIdx) {
|
||||
int accessFlags = reader.readU2();
|
||||
int nameIdx = reader.readU2();
|
||||
int descriptorIdx = reader.readU2();
|
||||
JavaAttrStorage attributes = attributesReader.load(reader);
|
||||
|
||||
JavaMethodRef methodRef = method.getMethodRef();
|
||||
methodRef.reset();
|
||||
methodRef.initUniqId(clsReader, clsIdx, nameIdx, descriptorIdx);
|
||||
methodRef.setName(constPoolReader.getUtf8(nameIdx));
|
||||
methodRef.setDescr(constPoolReader.getUtf8(descriptorIdx));
|
||||
|
||||
if (methodRef.getName().equals("<init>")) {
|
||||
accessFlags |= AccessFlags.CONSTRUCTOR; // java bytecode don't use that flag
|
||||
}
|
||||
|
||||
method.setData(accessFlags, attributes);
|
||||
}
|
||||
|
||||
public DataReader getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IJadxAttribute> getAttributes() {
|
||||
data.absPos(offsets.getAttributesOffset());
|
||||
JavaAttrStorage attributes = attributesReader.load(data);
|
||||
int size = attributes.size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<IJadxAttribute> list = new ArrayList<>(size);
|
||||
Utils.addToList(list, JavaAnnotationsAttr.merge(attributes));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.INNER_CLASSES));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.SOURCE_FILE));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE));
|
||||
return list;
|
||||
}
|
||||
|
||||
public <T extends IJavaAttribute> T loadAttribute(DataReader reader, JavaAttrType<T> type) {
|
||||
reader.absPos(offsets.getAttributesOffset());
|
||||
return attributesReader.loadOne(type, reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisassembledCode() {
|
||||
return DisasmUtils.get(data.getBytes());
|
||||
}
|
||||
|
||||
public JavaClassReader getClsReader() {
|
||||
return clsReader;
|
||||
}
|
||||
|
||||
public ClassOffsets getOffsets() {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
public ConstPoolReader getConstPoolReader() {
|
||||
return constPoolReader;
|
||||
}
|
||||
|
||||
public AttributesReader getAttributesReader() {
|
||||
return attributesReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getInputFileName();
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
import jadx.plugins.input.java.data.attributes.types.ConstValueAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr;
|
||||
|
||||
public class JavaFieldData implements IFieldData {
|
||||
private String name;
|
||||
private String parentClassType;
|
||||
private String type;
|
||||
private int accessFlags;
|
||||
private JavaAttrStorage attributes;
|
||||
|
||||
@Override
|
||||
public String getParentClassType() {
|
||||
return parentClassType;
|
||||
}
|
||||
|
||||
public void setParentClassType(String parentClassType) {
|
||||
this.parentClassType = parentClassType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public void setAccessFlags(int accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
public void setAttributes(JavaAttrStorage attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IJadxAttribute> getAttributes() {
|
||||
int size = attributes.size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<IJadxAttribute> list = new ArrayList<>(size);
|
||||
Utils.addToList(list, JavaAnnotationsAttr.merge(attributes));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.CONST_VALUE), ConstValueAttr::getValue);
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClassType + "->" + name + ":" + type;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
import jadx.plugins.input.java.data.attributes.types.CodeAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationDefaultAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaParamAnnsAttr;
|
||||
import jadx.plugins.input.java.data.code.JavaCodeReader;
|
||||
|
||||
public class JavaMethodData implements IMethodData {
|
||||
|
||||
private final JavaClassData clsData;
|
||||
private final JavaMethodRef methodRef;
|
||||
private int accessFlags;
|
||||
private JavaAttrStorage attributes;
|
||||
|
||||
public JavaMethodData(JavaClassData clsData, JavaMethodRef methodRef) {
|
||||
this.clsData = clsData;
|
||||
this.methodRef = methodRef;
|
||||
}
|
||||
|
||||
public void setData(int accessFlags, JavaAttrStorage attributes) {
|
||||
this.accessFlags = accessFlags;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaMethodRef getMethodRef() {
|
||||
return methodRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeReader getCodeReader() {
|
||||
CodeAttr codeAttr = attributes.get(JavaAttrType.CODE);
|
||||
if (codeAttr == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaCodeReader(clsData, codeAttr.getOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String disassembleMethod() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IJadxAttribute> getAttributes() {
|
||||
int size = attributes.size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<IJadxAttribute> list = new ArrayList<>(size);
|
||||
Utils.addToList(list, JavaAnnotationsAttr.merge(attributes));
|
||||
Utils.addToList(list, JavaParamAnnsAttr.merge(attributes));
|
||||
Utils.addToList(list, JavaAnnotationDefaultAttr.convert(attributes));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.SIGNATURE));
|
||||
Utils.addToList(list, attributes.get(JavaAttrType.EXCEPTIONS));
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getMethodRef().toString();
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodProto;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
|
||||
public class JavaMethodProto implements IMethodProto {
|
||||
|
||||
private String returnType;
|
||||
private List<String> argTypes;
|
||||
|
||||
@Override
|
||||
public String getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(String returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getArgTypes() {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public void setArgTypes(List<String> argTypes) {
|
||||
this.argTypes = argTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + Utils.listToStr(argTypes) + ")" + returnType;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package jadx.plugins.input.java.data;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.plugins.input.java.JavaClassReader;
|
||||
import jadx.plugins.input.java.utils.DescriptorParser;
|
||||
|
||||
public class JavaMethodRef extends JavaMethodProto implements IMethodRef {
|
||||
|
||||
private int uniqId;
|
||||
private String parentClassType;
|
||||
private String name;
|
||||
private String descr;
|
||||
|
||||
@Override
|
||||
public int getUniqId() {
|
||||
return uniqId;
|
||||
}
|
||||
|
||||
public void initUniqId(JavaClassReader clsReader, int clsIdx, int nameIdx, int descIdx) {
|
||||
// TODO: check for id overlap
|
||||
this.uniqId = (clsReader.getId() & 0xFFF) << 20 | (clsIdx & 0xFF) << 12 | (nameIdx & 0xFF) << 4 | descIdx & 0xF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentClassType() {
|
||||
return parentClassType;
|
||||
}
|
||||
|
||||
public void setParentClassType(String parentClassType) {
|
||||
this.parentClassType = parentClassType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescriptor() {
|
||||
return descr;
|
||||
}
|
||||
|
||||
public void setDescr(String descr) {
|
||||
this.descr = descr;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.setReturnType(null);
|
||||
this.setArgTypes(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (getReturnType() == null) {
|
||||
DescriptorParser.fillMethodProto(descr, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parentClassType + "->" + name + descr;
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
|
||||
public class AttributesReader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AttributesReader.class);
|
||||
|
||||
private final JavaClassData clsData;
|
||||
private final ConstPoolReader constPool;
|
||||
private final Map<Integer, JavaAttrType<?>> attrMap = new HashMap<>(JavaAttrType.size());
|
||||
|
||||
public AttributesReader(JavaClassData clsData, ConstPoolReader constPoolReader) {
|
||||
this.clsData = clsData;
|
||||
this.constPool = constPoolReader;
|
||||
}
|
||||
|
||||
public JavaAttrStorage load(DataReader reader) {
|
||||
int attributesCount = reader.readU2();
|
||||
if (attributesCount == 0) {
|
||||
return JavaAttrStorage.EMPTY;
|
||||
}
|
||||
JavaAttrStorage storage = new JavaAttrStorage();
|
||||
for (int i = 0; i < attributesCount; i++) {
|
||||
readAndAdd(storage, reader);
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
private void readAndAdd(JavaAttrStorage storage, DataReader reader) {
|
||||
int nameIdx = reader.readU2();
|
||||
int len = reader.readU4();
|
||||
int end = reader.getOffset() + len;
|
||||
try {
|
||||
JavaAttrType<?> attrType = resolveAttrReader(nameIdx);
|
||||
if (attrType == null) {
|
||||
return;
|
||||
}
|
||||
IJavaAttributeReader attrReader = attrType.getReader();
|
||||
if (attrReader == null) {
|
||||
// ignore attribute
|
||||
return;
|
||||
}
|
||||
IJavaAttribute attrValue = attrReader.read(clsData, reader);
|
||||
if (attrValue != null) {
|
||||
storage.add(attrType, attrValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse attribute: {}", constPool.getUtf8(nameIdx), e);
|
||||
} finally {
|
||||
reader.absPos(end);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T extends IJavaAttribute> T loadOne(JavaAttrType<T> type, DataReader reader) {
|
||||
int attributesCount = reader.readU2();
|
||||
if (attributesCount == 0) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < attributesCount; i++) {
|
||||
IJavaAttribute attr = readType(type, reader);
|
||||
if (attr != null) {
|
||||
return (T) attr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IJavaAttribute readType(JavaAttrType<?> type, DataReader reader) {
|
||||
int nameIdx = reader.readU2();
|
||||
int len = reader.readU4();
|
||||
int end = reader.getOffset() + len;
|
||||
try {
|
||||
JavaAttrType<?> attrType = resolveAttrReader(nameIdx);
|
||||
if (attrType == null || attrType != type) {
|
||||
return null;
|
||||
}
|
||||
return attrType.getReader().read(clsData, reader);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse attribute: {}", constPool.getUtf8(nameIdx), e);
|
||||
return null;
|
||||
} finally {
|
||||
reader.absPos(end);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaAttrType<?> resolveAttrReader(int nameIdx) {
|
||||
return attrMap.computeIfAbsent(nameIdx, idx -> {
|
||||
String attrName = constPool.getUtf8(nameIdx);
|
||||
JavaAttrType<?> attrType = JavaAttrType.byName(attrName);
|
||||
if (attrType == null) {
|
||||
LOG.warn("Unknown java class attribute type: {}", attrName);
|
||||
return null;
|
||||
}
|
||||
return attrType;
|
||||
});
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.impl.JadxFieldRef;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class EncodedValueReader {
|
||||
|
||||
public static EncodedValue read(JavaClassData clsData, DataReader reader) {
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
char tag = (char) reader.readU1();
|
||||
switch (tag) {
|
||||
case 'B':
|
||||
return new EncodedValue(EncodedType.ENCODED_BYTE, (byte) constPool.getInt(reader.readU2()));
|
||||
case 'C':
|
||||
return new EncodedValue(EncodedType.ENCODED_CHAR, (char) constPool.getInt(reader.readU2()));
|
||||
case 'D':
|
||||
return new EncodedValue(EncodedType.ENCODED_DOUBLE, constPool.getDouble(reader.readU2()));
|
||||
case 'F':
|
||||
return new EncodedValue(EncodedType.ENCODED_FLOAT, constPool.getFloat(reader.readU2()));
|
||||
case 'I':
|
||||
return new EncodedValue(EncodedType.ENCODED_INT, constPool.getInt(reader.readU2()));
|
||||
case 'J':
|
||||
return new EncodedValue(EncodedType.ENCODED_LONG, constPool.getLong(reader.readU2()));
|
||||
case 'S':
|
||||
return new EncodedValue(EncodedType.ENCODED_SHORT, (short) constPool.getInt(reader.readU2()));
|
||||
case 'Z':
|
||||
return new EncodedValue(EncodedType.ENCODED_BOOLEAN, 1 == constPool.getInt(reader.readU2()));
|
||||
case 's':
|
||||
return new EncodedValue(EncodedType.ENCODED_STRING, constPool.getUtf8(reader.readU2()));
|
||||
|
||||
case 'e':
|
||||
String cls = constPool.getUtf8(reader.readU2());
|
||||
String name = constPool.getUtf8(reader.readU2());
|
||||
return new EncodedValue(EncodedType.ENCODED_ENUM, new JadxFieldRef(cls, name, cls));
|
||||
|
||||
case 'c':
|
||||
return new EncodedValue(EncodedType.ENCODED_TYPE, constPool.getUtf8(reader.readU2()));
|
||||
|
||||
case '@':
|
||||
return new EncodedValue(EncodedType.ENCODED_ANNOTATION,
|
||||
JavaAnnotationsAttr.readAnnotation(AnnotationVisibility.RUNTIME, clsData, reader));
|
||||
|
||||
case '[':
|
||||
int len = reader.readU2();
|
||||
List<EncodedValue> values = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
values.add(read(clsData, reader));
|
||||
}
|
||||
return new EncodedValue(EncodedType.ENCODED_ARRAY, values);
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Unknown element value tag: " + tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
public interface IJavaAttribute {
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
|
||||
public interface IJavaAttributeReader {
|
||||
IJavaAttribute read(JavaClassData clsData, DataReader reader);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JavaAttrStorage {
|
||||
public static final JavaAttrStorage EMPTY = new JavaAttrStorage();
|
||||
|
||||
private final IJavaAttribute[] map = new IJavaAttribute[JavaAttrType.size()];
|
||||
|
||||
public void add(JavaAttrType<?> type, IJavaAttribute value) {
|
||||
map[type.getId()] = value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <A extends IJavaAttribute> A get(JavaAttrType<A> type) {
|
||||
return (A) map[type.getId()];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int size = 0;
|
||||
for (IJavaAttribute attr : map) {
|
||||
if (attr != null) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AttributesStorage{size=" + size() + '}';
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package jadx.plugins.input.java.data.attributes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LineNumberTableAttr;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarTypesAttr;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.CodeAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.ConstValueAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.IgnoredAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationDefaultAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaAnnotationsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaBootstrapMethodsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaExceptionsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaInnerClsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaParamAnnsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaSignatureAttr;
|
||||
import jadx.plugins.input.java.data.attributes.types.JavaSourceFileAttr;
|
||||
|
||||
public final class JavaAttrType<T extends IJavaAttribute> {
|
||||
|
||||
private static final Map<String, JavaAttrType<?>> NAME_TO_TYPE_MAP;
|
||||
|
||||
public static final JavaAttrType<JavaInnerClsAttr> INNER_CLASSES;
|
||||
public static final JavaAttrType<JavaBootstrapMethodsAttr> BOOTSTRAP_METHODS;
|
||||
|
||||
public static final JavaAttrType<ConstValueAttr> CONST_VALUE;
|
||||
|
||||
public static final JavaAttrType<CodeAttr> CODE;
|
||||
public static final JavaAttrType<LineNumberTableAttr> LINE_NUMBER_TABLE;
|
||||
public static final JavaAttrType<LocalVarsAttr> LOCAL_VAR_TABLE;
|
||||
public static final JavaAttrType<LocalVarTypesAttr> LOCAL_VAR_TYPE_TABLE;
|
||||
|
||||
public static final JavaAttrType<JavaAnnotationsAttr> RUNTIME_ANNOTATIONS;
|
||||
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<JavaAnnotationDefaultAttr> ANNOTATION_DEFAULT;
|
||||
|
||||
public static final JavaAttrType<JavaSourceFileAttr> SOURCE_FILE;
|
||||
public static final JavaAttrType<JavaSignatureAttr> SIGNATURE;
|
||||
public static final JavaAttrType<JavaExceptionsAttr> EXCEPTIONS;
|
||||
|
||||
public static final JavaAttrType<IgnoredAttr> DEPRECATED;
|
||||
public static final JavaAttrType<IgnoredAttr> STACK_MAP_TABLE;
|
||||
|
||||
static {
|
||||
NAME_TO_TYPE_MAP = new HashMap<>();
|
||||
|
||||
CONST_VALUE = bind("ConstantValue", ConstValueAttr.reader());
|
||||
|
||||
CODE = bind("Code", CodeAttr.reader());
|
||||
|
||||
LINE_NUMBER_TABLE = bind("LineNumberTable", LineNumberTableAttr.reader());
|
||||
LOCAL_VAR_TABLE = bind("LocalVariableTable", LocalVarsAttr.reader());
|
||||
LOCAL_VAR_TYPE_TABLE = bind("LocalVariableTypeTable", LocalVarTypesAttr.reader());
|
||||
|
||||
INNER_CLASSES = bind("InnerClasses", JavaInnerClsAttr.reader());
|
||||
BOOTSTRAP_METHODS = bind("BootstrapMethods", JavaBootstrapMethodsAttr.reader());
|
||||
|
||||
RUNTIME_ANNOTATIONS = bind("RuntimeVisibleAnnotations", JavaAnnotationsAttr.reader(AnnotationVisibility.RUNTIME));
|
||||
BUILD_ANNOTATIONS = bind("RuntimeInvisibleAnnotations", JavaAnnotationsAttr.reader(AnnotationVisibility.BUILD));
|
||||
RUNTIME_PARAMETER_ANNOTATIONS = bind("RuntimeVisibleParameterAnnotations", JavaParamAnnsAttr.reader(AnnotationVisibility.RUNTIME));
|
||||
BUILD_PARAMETER_ANNOTATIONS = bind("RuntimeInvisibleParameterAnnotations", JavaParamAnnsAttr.reader(AnnotationVisibility.BUILD));
|
||||
ANNOTATION_DEFAULT = bind("AnnotationDefault", JavaAnnotationDefaultAttr.reader());
|
||||
|
||||
SOURCE_FILE = bind("SourceFile", JavaSourceFileAttr.reader());
|
||||
SIGNATURE = bind("Signature", JavaSignatureAttr.reader());
|
||||
EXCEPTIONS = bind("Exceptions", JavaExceptionsAttr.reader());
|
||||
|
||||
// ignored
|
||||
DEPRECATED = bind("Deprecated", null); // duplicated by annotation
|
||||
STACK_MAP_TABLE = bind("StackMapTable", null);
|
||||
}
|
||||
|
||||
private static <A extends IJavaAttribute> JavaAttrType<A> bind(String name, IJavaAttributeReader reader) {
|
||||
JavaAttrType<A> attrType = new JavaAttrType<>(NAME_TO_TYPE_MAP.size(), name, reader);
|
||||
NAME_TO_TYPE_MAP.put(name, attrType);
|
||||
return attrType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaAttrType<?> byName(String name) {
|
||||
return NAME_TO_TYPE_MAP.get(name);
|
||||
}
|
||||
|
||||
public static int size() {
|
||||
return NAME_TO_TYPE_MAP.size();
|
||||
}
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final IJavaAttributeReader reader;
|
||||
|
||||
private JavaAttrType(int id, String name, IJavaAttributeReader reader) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public IJavaAttributeReader getReader() {
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package jadx.plugins.input.java.data.attributes.debuginfo;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.ILocalVar;
|
||||
|
||||
public class JavaLocalVar implements ILocalVar {
|
||||
private int regNum;
|
||||
private final String name;
|
||||
private final String type;
|
||||
@Nullable
|
||||
private String sign;
|
||||
|
||||
private final int startOffset;
|
||||
private final int endOffset;
|
||||
|
||||
public JavaLocalVar(int regNum, String name, @Nullable String type, @Nullable String sign, int startOffset, int endOffset) {
|
||||
this.regNum = regNum;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.sign = sign;
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
|
||||
public void shiftRegNum(int maxStack) {
|
||||
this.regNum += maxStack; // convert local var to register
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegNum() {
|
||||
return regNum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSignature() {
|
||||
return sign;
|
||||
}
|
||||
|
||||
public void setSignature(String sign) {
|
||||
this.sign = sign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndOffset() {
|
||||
return endOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = regNum;
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + startOffset;
|
||||
result = 31 * result + endOffset;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JavaLocalVar)) {
|
||||
return false;
|
||||
}
|
||||
JavaLocalVar other = (JavaLocalVar) o;
|
||||
return regNum == other.regNum
|
||||
&& startOffset == other.startOffset
|
||||
&& endOffset == other.endOffset
|
||||
&& name.equals(other.name);
|
||||
}
|
||||
|
||||
private static String formatOffset(int offset) {
|
||||
return String.format("0x%04x", offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formatOffset(startOffset) + '-' + formatOffset(endOffset)
|
||||
+ ": r" + regNum + " '" + name + "' " + type
|
||||
+ (sign != null ? ", signature: " + sign : "");
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package jadx.plugins.input.java.data.attributes.debuginfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class LineNumberTableAttr implements IJavaAttribute {
|
||||
private final Map<Integer, Integer> lineMap;
|
||||
|
||||
public LineNumberTableAttr(Map<Integer, Integer> sourceLineMap) {
|
||||
this.lineMap = sourceLineMap;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getLineMap() {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> {
|
||||
int len = reader.readU2();
|
||||
Map<Integer, Integer> map = new HashMap<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int offset = reader.readU2();
|
||||
int line = reader.readU2();
|
||||
map.put(offset, line);
|
||||
}
|
||||
return new LineNumberTableAttr(map);
|
||||
};
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package jadx.plugins.input.java.data.attributes.debuginfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class LocalVarTypesAttr implements IJavaAttribute {
|
||||
private final List<JavaLocalVar> vars;
|
||||
|
||||
public LocalVarTypesAttr(List<JavaLocalVar> vars) {
|
||||
this.vars = vars;
|
||||
}
|
||||
|
||||
public List<JavaLocalVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> {
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
int len = reader.readU2();
|
||||
List<JavaLocalVar> varsList = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int startOffset = reader.readU2();
|
||||
int endOffset = startOffset + reader.readU2() - 1;
|
||||
int nameIdx = reader.readU2();
|
||||
int typeIdx = reader.readU2();
|
||||
int varNum = reader.readU2();
|
||||
varsList.add(new JavaLocalVar(
|
||||
varNum,
|
||||
constPool.getUtf8(nameIdx),
|
||||
null,
|
||||
constPool.getUtf8(typeIdx),
|
||||
startOffset, endOffset));
|
||||
}
|
||||
return new LocalVarTypesAttr(varsList);
|
||||
};
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package jadx.plugins.input.java.data.attributes.debuginfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class LocalVarsAttr implements IJavaAttribute {
|
||||
private final List<JavaLocalVar> vars;
|
||||
|
||||
public LocalVarsAttr(List<JavaLocalVar> vars) {
|
||||
this.vars = vars;
|
||||
}
|
||||
|
||||
public List<JavaLocalVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> {
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
int count = reader.readU2();
|
||||
List<JavaLocalVar> varsList = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int startOffset = reader.readU2();
|
||||
int length = reader.readU2();
|
||||
int endOffset = startOffset + length - 1;
|
||||
int nameIdx = reader.readU2();
|
||||
int typeIdx = reader.readU2();
|
||||
int varNum = reader.readU2();
|
||||
varsList.add(new JavaLocalVar(varNum,
|
||||
constPool.getUtf8(nameIdx),
|
||||
constPool.getUtf8(typeIdx),
|
||||
null,
|
||||
startOffset, endOffset));
|
||||
}
|
||||
return new LocalVarsAttr(varsList);
|
||||
};
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class CodeAttr implements IJavaAttribute {
|
||||
private final int offset;
|
||||
|
||||
public CodeAttr(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new CodeAttr(reader.getOffset());
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class ConstValueAttr implements IJavaAttribute {
|
||||
|
||||
private final EncodedValue value;
|
||||
|
||||
public ConstValueAttr(EncodedValue value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public EncodedValue getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new ConstValueAttr(clsData.getConstPoolReader().readAsEncodedValue(reader.readU2()));
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class IgnoredAttr implements IJavaAttribute {
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
|
||||
import jadx.plugins.input.java.data.attributes.EncodedValueReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
|
||||
public class JavaAnnotationDefaultAttr extends AnnotationDefaultAttr implements IJavaAttribute {
|
||||
|
||||
public JavaAnnotationDefaultAttr(EncodedValue value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new JavaAnnotationDefaultAttr(EncodedValueReader.read(clsData, reader));
|
||||
}
|
||||
|
||||
public static AnnotationDefaultAttr convert(JavaAttrStorage attributes) {
|
||||
return attributes.get(JavaAttrType.ANNOTATION_DEFAULT);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.api.plugins.input.data.annotations.JadxAnnotation;
|
||||
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
import jadx.plugins.input.java.data.attributes.EncodedValueReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
|
||||
public class JavaAnnotationsAttr implements IJavaAttribute {
|
||||
private final List<IAnnotation> list;
|
||||
|
||||
public JavaAnnotationsAttr(List<IAnnotation> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public List<IAnnotation> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader(AnnotationVisibility visibility) {
|
||||
return (clsData, reader) -> new JavaAnnotationsAttr(readAnnotationsList(visibility, clsData, reader));
|
||||
}
|
||||
|
||||
public static List<IAnnotation> readAnnotationsList(AnnotationVisibility visibility, JavaClassData clsData, DataReader reader) {
|
||||
int len = reader.readU2();
|
||||
List<IAnnotation> list = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
list.add(readAnnotation(visibility, clsData, reader));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static JadxAnnotation readAnnotation(AnnotationVisibility visibility, JavaClassData clsData, DataReader reader) {
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
String type = constPool.getUtf8(reader.readU2());
|
||||
int pairsCount = reader.readU2();
|
||||
Map<String, EncodedValue> pairs = new LinkedHashMap<>(pairsCount);
|
||||
for (int j = 0; j < pairsCount; j++) {
|
||||
String name = constPool.getUtf8(reader.readU2());
|
||||
EncodedValue value = EncodedValueReader.read(clsData, reader);
|
||||
pairs.put(name, value);
|
||||
}
|
||||
return new JadxAnnotation(visibility, type, pairs);
|
||||
}
|
||||
|
||||
public static AnnotationsAttr merge(JavaAttrStorage storage) {
|
||||
JavaAnnotationsAttr runtimeAnnAttr = storage.get(JavaAttrType.RUNTIME_ANNOTATIONS);
|
||||
JavaAnnotationsAttr buildAnnAttr = storage.get(JavaAttrType.BUILD_ANNOTATIONS);
|
||||
if (runtimeAnnAttr == null && buildAnnAttr == null) {
|
||||
return null;
|
||||
}
|
||||
if (buildAnnAttr == null) {
|
||||
return AnnotationsAttr.pack(runtimeAnnAttr.getList());
|
||||
}
|
||||
if (runtimeAnnAttr == null) {
|
||||
return AnnotationsAttr.pack(buildAnnAttr.getList());
|
||||
}
|
||||
return AnnotationsAttr.pack(Utils.concat(runtimeAnnAttr.getList(), buildAnnAttr.getList()));
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
import jadx.plugins.input.java.data.attributes.types.data.RawBootstrapMethod;
|
||||
|
||||
public class JavaBootstrapMethodsAttr implements IJavaAttribute {
|
||||
|
||||
private final List<RawBootstrapMethod> list;
|
||||
|
||||
public JavaBootstrapMethodsAttr(List<RawBootstrapMethod> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public List<RawBootstrapMethod> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> {
|
||||
int len = reader.readU2();
|
||||
List<RawBootstrapMethod> list = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int methodHandleIdx = reader.readU2();
|
||||
int argsCount = reader.readU2();
|
||||
int[] args = new int[argsCount];
|
||||
for (int j = 0; j < argsCount; j++) {
|
||||
args[j] = reader.readU2();
|
||||
}
|
||||
list.add(new RawBootstrapMethod(methodHandleIdx, args));
|
||||
}
|
||||
return new JavaBootstrapMethodsAttr(list);
|
||||
};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class JavaExceptionsAttr extends ExceptionsAttr implements IJavaAttribute {
|
||||
public JavaExceptionsAttr(List<String> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new JavaExceptionsAttr(reader.readClassesList(clsData.getConstPoolReader()));
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr;
|
||||
import jadx.api.plugins.input.data.attributes.types.InnerClsInfo;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class JavaInnerClsAttr extends InnerClassesAttr implements IJavaAttribute {
|
||||
|
||||
public JavaInnerClsAttr(Map<String, InnerClsInfo> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> {
|
||||
int len = reader.readU2();
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
Map<String, InnerClsInfo> clsMap = new HashMap<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
String innerCls = constPool.getClass(reader.readU2());
|
||||
int outerClsIdx = reader.readU2();
|
||||
String outerCls = outerClsIdx == 0 ? null : constPool.getClass(outerClsIdx);
|
||||
String name = constPool.getUtf8(reader.readU2());
|
||||
int accFlags = reader.readU2();
|
||||
clsMap.put(innerCls, new InnerClsInfo(innerCls, outerCls, name, accFlags));
|
||||
}
|
||||
return new JavaInnerClsAttr(clsMap);
|
||||
};
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.api.plugins.input.data.attributes.types.MethodParamsAttr;
|
||||
import jadx.api.plugins.utils.Utils;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
|
||||
public class JavaParamAnnsAttr implements IJavaAttribute {
|
||||
private final List<List<IAnnotation>> list;
|
||||
|
||||
public JavaParamAnnsAttr(List<List<IAnnotation>> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public List<List<IAnnotation>> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader(AnnotationVisibility visibility) {
|
||||
return (clsData, reader) -> {
|
||||
int len = reader.readU1();
|
||||
List<List<IAnnotation>> list = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
list.add(JavaAnnotationsAttr.readAnnotationsList(visibility, clsData, reader));
|
||||
}
|
||||
return new JavaParamAnnsAttr(list);
|
||||
};
|
||||
}
|
||||
|
||||
public static MethodParamsAttr merge(JavaAttrStorage storage) {
|
||||
JavaParamAnnsAttr runtimeAnnAttr = storage.get(JavaAttrType.RUNTIME_PARAMETER_ANNOTATIONS);
|
||||
JavaParamAnnsAttr buildAnnAttr = storage.get(JavaAttrType.BUILD_PARAMETER_ANNOTATIONS);
|
||||
if (runtimeAnnAttr == null && buildAnnAttr == null) {
|
||||
return null;
|
||||
}
|
||||
if (buildAnnAttr == null) {
|
||||
return MethodParamsAttr.pack(runtimeAnnAttr.getList());
|
||||
}
|
||||
if (runtimeAnnAttr == null) {
|
||||
return MethodParamsAttr.pack(buildAnnAttr.getList());
|
||||
}
|
||||
return MethodParamsAttr.pack(mergeParamLists(runtimeAnnAttr.getList(), buildAnnAttr.getList()));
|
||||
}
|
||||
|
||||
private static List<List<IAnnotation>> mergeParamLists(List<List<IAnnotation>> first, List<List<IAnnotation>> second) {
|
||||
int firstSize = first.size();
|
||||
int secondSize = second.size();
|
||||
int size = Math.max(firstSize, secondSize);
|
||||
List<List<IAnnotation>> result = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
List<IAnnotation> firstList = i < firstSize ? first.get(i) : Collections.emptyList();
|
||||
List<IAnnotation> secondList = i < secondSize ? second.get(i) : Collections.emptyList();
|
||||
result.add(Utils.concat(firstList, secondList));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.types.SignatureAttr;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class JavaSignatureAttr extends SignatureAttr implements IJavaAttribute {
|
||||
|
||||
public JavaSignatureAttr(String signature) {
|
||||
super(signature);
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new JavaSignatureAttr(clsData.getConstPoolReader().getUtf8(reader.readU2()));
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package jadx.plugins.input.java.data.attributes.types;
|
||||
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
|
||||
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
|
||||
|
||||
public class JavaSourceFileAttr extends SourceFileAttr implements IJavaAttribute {
|
||||
|
||||
public JavaSourceFileAttr(String fileName) {
|
||||
super(fileName);
|
||||
}
|
||||
|
||||
public static IJavaAttributeReader reader() {
|
||||
return (clsData, reader) -> new JavaSourceFileAttr(clsData.getConstPoolReader().getUtf8(reader.readU2()));
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package jadx.plugins.input.java.data.attributes.types.data;
|
||||
|
||||
public class RawBootstrapMethod {
|
||||
private final int methodHandleIdx;
|
||||
private final int[] args;
|
||||
|
||||
public RawBootstrapMethod(int methodHandleIdx, int[] args) {
|
||||
this.methodHandleIdx = methodHandleIdx;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public int getMethodHandleIdx() {
|
||||
return methodHandleIdx;
|
||||
}
|
||||
|
||||
public int[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class ArrayType {
|
||||
|
||||
public static String byValue(int val) {
|
||||
switch (val) {
|
||||
case 4:
|
||||
return "Z";
|
||||
case 5:
|
||||
return "C";
|
||||
case 6:
|
||||
return "F";
|
||||
case 7:
|
||||
return "D";
|
||||
case 8:
|
||||
return "B";
|
||||
case 9:
|
||||
return "S";
|
||||
case 10:
|
||||
return "I";
|
||||
case 11:
|
||||
return "J";
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Unknown array type value: " + val);
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
import jadx.plugins.input.java.data.code.StackState.SVType;
|
||||
|
||||
public class CodeDecodeState {
|
||||
private final JavaClassData clsData;
|
||||
private final DataReader reader;
|
||||
private final int maxStack;
|
||||
private final Set<Integer> excHandlers;
|
||||
|
||||
private final Map<Integer, StackState> jumpStack = new HashMap<>(); // save current stack for jump target
|
||||
|
||||
private JavaInsnData insn;
|
||||
private StackState stack;
|
||||
private boolean excHandler;
|
||||
|
||||
public CodeDecodeState(JavaClassData clsData, DataReader reader, int maxStack, Set<Integer> excHandlers) {
|
||||
this.clsData = clsData;
|
||||
this.reader = reader;
|
||||
this.maxStack = maxStack;
|
||||
this.excHandlers = excHandlers;
|
||||
this.stack = new StackState(maxStack);
|
||||
}
|
||||
|
||||
public void onInsn(int offset) {
|
||||
StackState stackState = jumpStack.get(offset);
|
||||
if (stackState != null) {
|
||||
this.stack = stackState;
|
||||
}
|
||||
if (excHandlers.contains(offset)) {
|
||||
stack.push(SVType.NARROW); // push exception
|
||||
excHandler = true;
|
||||
} else {
|
||||
excHandler = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void registerJump(int jumpOffset) {
|
||||
Integer key = jumpOffset;
|
||||
if (!jumpStack.containsKey(key)) {
|
||||
jumpStack.put(key, stack.copy());
|
||||
}
|
||||
}
|
||||
|
||||
public void decoded() {
|
||||
if (excHandler && insn.getOpcode() == Opcode.MOVE) {
|
||||
// replace first 'move' in exception handler with 'move-exception'
|
||||
insn.setOpcode(Opcode.MOVE_EXCEPTION);
|
||||
insn.setRegsCount(1);
|
||||
}
|
||||
}
|
||||
|
||||
public JavaInsnData insn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
public void setInsn(JavaInsnData insn) {
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
public DataReader reader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
public JavaClassData clsData() {
|
||||
return clsData;
|
||||
}
|
||||
|
||||
public CodeDecodeState local(int arg, int local) {
|
||||
insn.setArgReg(arg, localToReg(local));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState pop(int arg) {
|
||||
insn.setArgReg(arg, stack.pop());
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState peek(int arg) {
|
||||
insn.setArgReg(arg, stack.peek());
|
||||
return this;
|
||||
}
|
||||
|
||||
public SVType peekType(int at) {
|
||||
return stack.peekTypeAt(at);
|
||||
}
|
||||
|
||||
public CodeDecodeState peekFrom(int pos, int arg) {
|
||||
insn.setArgReg(arg, stack.peekAt(pos));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState push(int arg) {
|
||||
insn.setArgReg(arg, stack.push(SVType.NARROW));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState push(int arg, SVType type) {
|
||||
insn.setArgReg(arg, stack.push(type));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState pushWide(int arg) {
|
||||
insn.setArgReg(arg, stack.push(SVType.WIDE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void discard() {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
public void discardWord() {
|
||||
SVType type = stack.peekTypeAt(0);
|
||||
stack.pop();
|
||||
if (type == SVType.NARROW) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
public int push(String type) {
|
||||
return stack.push(getSVType(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be after all pop and push
|
||||
*/
|
||||
public void jump(int offset) {
|
||||
int jumpOffset = insn.getOffset() + offset;
|
||||
insn.setTarget(jumpOffset);
|
||||
registerJump(jumpOffset);
|
||||
}
|
||||
|
||||
public CodeDecodeState idx(int idx) {
|
||||
insn.setIndex(idx);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeDecodeState lit(long lit) {
|
||||
insn.setLiteral(lit);
|
||||
return this;
|
||||
}
|
||||
|
||||
private int localToReg(int local) {
|
||||
return maxStack + local;
|
||||
}
|
||||
|
||||
public SVType fieldType() {
|
||||
String type = insn.constPoolReader().getFieldType(insn().getIndex());
|
||||
return getSVType(type);
|
||||
}
|
||||
|
||||
public SVType getSVType(String type) {
|
||||
if (type.equals("J") || type.equals("D")) {
|
||||
return SVType.WIDE;
|
||||
}
|
||||
return SVType.NARROW;
|
||||
}
|
||||
|
||||
public int u1() {
|
||||
return reader.readU1();
|
||||
}
|
||||
|
||||
public int u2() {
|
||||
return reader.readU2();
|
||||
}
|
||||
|
||||
public int s1() {
|
||||
return reader.readS1();
|
||||
}
|
||||
|
||||
public int s2() {
|
||||
return reader.readS2();
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IDebugInfo;
|
||||
import jadx.api.plugins.input.data.ILocalVar;
|
||||
import jadx.api.plugins.input.data.ITry;
|
||||
import jadx.api.plugins.input.data.impl.CatchData;
|
||||
import jadx.api.plugins.input.data.impl.DebugInfo;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.JavaClassData;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrStorage;
|
||||
import jadx.plugins.input.java.data.attributes.JavaAttrType;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.JavaLocalVar;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LineNumberTableAttr;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarTypesAttr;
|
||||
import jadx.plugins.input.java.data.attributes.debuginfo.LocalVarsAttr;
|
||||
import jadx.plugins.input.java.data.code.trycatch.JavaSingleCatch;
|
||||
import jadx.plugins.input.java.data.code.trycatch.JavaTryData;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class JavaCodeReader implements ICodeReader {
|
||||
|
||||
private final JavaClassData clsData;
|
||||
private final DataReader reader;
|
||||
private final int codeOffset;
|
||||
|
||||
public JavaCodeReader(JavaClassData clsData, int offset) {
|
||||
this.clsData = clsData;
|
||||
this.reader = clsData.getData();
|
||||
this.codeOffset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeReader copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInstructions(Consumer<InsnData> insnConsumer) {
|
||||
Set<Integer> excHandlers = getExcHandlers();
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
reader.skip(2);
|
||||
int codeSize = reader.readU4();
|
||||
|
||||
CodeDecodeState state = new CodeDecodeState(clsData, reader, maxStack, excHandlers);
|
||||
JavaInsnData insn = new JavaInsnData(state);
|
||||
state.setInsn(insn);
|
||||
int offset = 0;
|
||||
while (offset < codeSize) {
|
||||
int opcode = reader.readU1();
|
||||
JavaInsnInfo insnInfo = JavaInsnsRegister.get(opcode);
|
||||
if (insnInfo == null) {
|
||||
throw new JavaClassParseException("Unknown opcode: 0x" + Integer.toHexString(opcode));
|
||||
}
|
||||
insn.setDecoded(false);
|
||||
insn.setInsnInfo(insnInfo);
|
||||
insn.setInsnStart(reader.getOffset());
|
||||
insn.setOffset(offset);
|
||||
insn.setInsnInfo(insnInfo);
|
||||
insn.setRegsCount(insnInfo.getRegsCount());
|
||||
insn.setOpcode(insnInfo.getApiOpcode());
|
||||
insn.setPayloadSize(insnInfo.getPayloadSize());
|
||||
insn.setOpcodeUnit(opcode);
|
||||
insn.setPayload(null);
|
||||
|
||||
state.onInsn(offset);
|
||||
insnConsumer.accept(insn);
|
||||
|
||||
int payloadSize = insn.getPayloadSize();
|
||||
if (!insn.isDecoded()) {
|
||||
if (payloadSize == -1) {
|
||||
insn.skip();
|
||||
payloadSize = insn.getPayloadSize();
|
||||
} else {
|
||||
reader.skip(payloadSize);
|
||||
}
|
||||
}
|
||||
offset += 1 + payloadSize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegistersCount() {
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
int maxLocals = reader.readU2();
|
||||
return maxStack + maxLocals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArgsStartReg() {
|
||||
reader.absPos(codeOffset);
|
||||
return reader.readU2(); // maxStack
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnitsCount() {
|
||||
return reader.absPos(codeOffset + 4).readU4();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IDebugInfo getDebugInfo() {
|
||||
reader.absPos(codeOffset);
|
||||
int maxStack = reader.readU2();
|
||||
reader.skip(2);
|
||||
reader.skip(reader.readU4());
|
||||
reader.skip(reader.readU2() * 8);
|
||||
|
||||
JavaAttrStorage attrs = clsData.getAttributesReader().load(reader);
|
||||
LineNumberTableAttr linesAttr = attrs.get(JavaAttrType.LINE_NUMBER_TABLE);
|
||||
LocalVarsAttr varsAttr = attrs.get(JavaAttrType.LOCAL_VAR_TABLE);
|
||||
if (linesAttr == null && varsAttr == null) {
|
||||
return null;
|
||||
}
|
||||
Map<Integer, Integer> linesMap = linesAttr != null ? linesAttr.getLineMap() : Collections.emptyMap();
|
||||
|
||||
List<ILocalVar> vars;
|
||||
if (varsAttr == null) {
|
||||
vars = Collections.emptyList();
|
||||
} else {
|
||||
List<JavaLocalVar> javaVars = varsAttr.getVars();
|
||||
LocalVarTypesAttr typedVars = attrs.get(JavaAttrType.LOCAL_VAR_TYPE_TABLE);
|
||||
if (typedVars != null && !typedVars.getVars().isEmpty()) {
|
||||
// merge signature from typedVars into javaVars
|
||||
Map<JavaLocalVar, JavaLocalVar> varsMap = new HashMap<>(javaVars.size());
|
||||
javaVars.forEach(v -> varsMap.put(v, v));
|
||||
for (JavaLocalVar typedVar : typedVars.getVars()) {
|
||||
JavaLocalVar jv = varsMap.get(typedVar);
|
||||
if (jv != null) {
|
||||
jv.setSignature(typedVar.getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
javaVars.forEach(v -> v.shiftRegNum(maxStack));
|
||||
vars = Collections.unmodifiableList(javaVars);
|
||||
}
|
||||
return new DebugInfo(linesMap, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCodeOffset() {
|
||||
return codeOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ITry> getTries() {
|
||||
skipToTries();
|
||||
int excTableLen = reader.readU2();
|
||||
if (excTableLen == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ConstPoolReader constPool = clsData.getConstPoolReader();
|
||||
Map<JavaTryData, List<JavaSingleCatch>> tries = new HashMap<>(excTableLen);
|
||||
for (int i = 0; i < excTableLen; i++) {
|
||||
int start = reader.readU2();
|
||||
int end = reader.readU2();
|
||||
int handler = reader.readU2();
|
||||
int type = reader.readU2();
|
||||
JavaTryData tryData = new JavaTryData(start, end);
|
||||
List<JavaSingleCatch> catches = tries.computeIfAbsent(tryData, k -> new ArrayList<>());
|
||||
if (type == 0) {
|
||||
catches.add(new JavaSingleCatch(handler, null));
|
||||
} else {
|
||||
catches.add(new JavaSingleCatch(handler, constPool.getClass(type)));
|
||||
}
|
||||
}
|
||||
return tries.entrySet().stream()
|
||||
.map(e -> {
|
||||
JavaTryData tryData = e.getKey();
|
||||
tryData.setCatch(convertSingleCatches(e.getValue()));
|
||||
return tryData;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static CatchData convertSingleCatches(List<JavaSingleCatch> list) {
|
||||
int allAddr = -1;
|
||||
for (JavaSingleCatch singleCatch : list) {
|
||||
if (singleCatch.getType() == null) {
|
||||
allAddr = singleCatch.getHandler();
|
||||
list.remove(singleCatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
int len = list.size();
|
||||
int[] addrs = new int[len];
|
||||
String[] types = new String[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
JavaSingleCatch singleCatch = list.get(i);
|
||||
addrs[i] = singleCatch.getHandler();
|
||||
types[i] = singleCatch.getType();
|
||||
}
|
||||
return new CatchData(addrs, types, allAddr);
|
||||
}
|
||||
|
||||
private Set<Integer> getExcHandlers() {
|
||||
skipToTries();
|
||||
int excTableLen = reader.readU2();
|
||||
if (excTableLen == 0) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Integer> set = new HashSet<>(excTableLen);
|
||||
for (int i = 0; i < excTableLen; i++) {
|
||||
reader.skip(4);
|
||||
int handler = reader.readU2();
|
||||
reader.skip(2);
|
||||
set.add(handler);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void skipToTries() {
|
||||
reader.absPos(codeOffset + 4);
|
||||
int codeSize = reader.readU4();
|
||||
reader.skip(codeSize);
|
||||
}
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.ICallSite;
|
||||
import jadx.api.plugins.input.data.IFieldRef;
|
||||
import jadx.api.plugins.input.data.IMethodHandle;
|
||||
import jadx.api.plugins.input.data.IMethodProto;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.api.plugins.input.insns.InsnIndexType;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.api.plugins.input.insns.custom.ICustomPayload;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder;
|
||||
|
||||
public class JavaInsnData implements InsnData {
|
||||
|
||||
private final CodeDecodeState state;
|
||||
|
||||
private JavaInsnInfo insnInfo;
|
||||
private Opcode opcode;
|
||||
private boolean decoded;
|
||||
private int opcodeUnit;
|
||||
private int payloadSize;
|
||||
private int insnStart;
|
||||
private int offset;
|
||||
private int regsCount;
|
||||
private int[] argsReg = new int[16];
|
||||
private int resultReg;
|
||||
private long literal;
|
||||
private int target;
|
||||
private int index;
|
||||
@Nullable
|
||||
private ICustomPayload payload;
|
||||
|
||||
public JavaInsnData(CodeDecodeState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode() {
|
||||
IJavaInsnDecoder decoder = insnInfo.getDecoder();
|
||||
if (decoder != null) {
|
||||
decoder.decode(state);
|
||||
state.decoded();
|
||||
}
|
||||
decoded = true;
|
||||
}
|
||||
|
||||
public void skip() {
|
||||
IJavaInsnDecoder decoder = insnInfo.getDecoder();
|
||||
if (decoder != null) {
|
||||
decoder.skip(state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFileOffset() {
|
||||
return insnStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Opcode getOpcode() {
|
||||
return opcode;
|
||||
}
|
||||
|
||||
public void setOpcode(Opcode opcode) {
|
||||
this.opcode = opcode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteCode() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnIndexType getIndexType() {
|
||||
return insnInfo.getIndexType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawOpcodeUnit() {
|
||||
return opcodeUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegsCount() {
|
||||
return regsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReg(int argNum) {
|
||||
return argsReg[argNum];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResultReg() {
|
||||
return resultReg;
|
||||
}
|
||||
|
||||
public void setResultReg(int resultReg) {
|
||||
this.resultReg = resultReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLiteral() {
|
||||
return literal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getPayloadSize() {
|
||||
return payloadSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexAsString() {
|
||||
return constPoolReader().getUtf8(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexAsType() {
|
||||
if (insnInfo.getOpcode() == 0xbc) { // newarray
|
||||
return ArrayType.byValue(index);
|
||||
}
|
||||
return constPoolReader().getClass(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IFieldRef getIndexAsField() {
|
||||
return constPoolReader().getFieldRef(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMethodRef getIndexAsMethod() {
|
||||
return constPoolReader().getMethodRef(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICallSite getIndexAsCallSite() {
|
||||
return constPoolReader().getCallSite(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMethodProto getIndexAsProto(int protoIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMethodHandle getIndexAsMethodHandle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICustomPayload getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setInsnInfo(JavaInsnInfo insnInfo) {
|
||||
this.insnInfo = insnInfo;
|
||||
}
|
||||
|
||||
public boolean isDecoded() {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public void setDecoded(boolean decoded) {
|
||||
this.decoded = decoded;
|
||||
}
|
||||
|
||||
public void setOpcodeUnit(int opcodeUnit) {
|
||||
this.opcodeUnit = opcodeUnit;
|
||||
}
|
||||
|
||||
public void setPayloadSize(int payloadSize) {
|
||||
this.payloadSize = payloadSize;
|
||||
}
|
||||
|
||||
public void setInsnStart(int insnStart) {
|
||||
this.insnStart = insnStart;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public void setArgReg(int arg, int reg) {
|
||||
this.argsReg[arg] = reg;
|
||||
}
|
||||
|
||||
public void setRegsCount(int regsCount) {
|
||||
this.regsCount = regsCount;
|
||||
if (argsReg.length < regsCount) {
|
||||
argsReg = new int[regsCount];
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getRegsArray() {
|
||||
return argsReg;
|
||||
}
|
||||
|
||||
public void setLiteral(long literal) {
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
public void setTarget(int target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public void setPayload(ICustomPayload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public ConstPoolReader constPoolReader() {
|
||||
return state.clsData().getConstPoolReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("0x%04X", offset));
|
||||
sb.append(": ").append(getOpcode());
|
||||
if (insnInfo == null) {
|
||||
sb.append(String.format("(0x%04X)", opcodeUnit));
|
||||
} else {
|
||||
int regsCount = getRegsCount();
|
||||
if (isDecoded()) {
|
||||
sb.append(' ');
|
||||
for (int i = 0; i < regsCount; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("r").append(argsReg[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import jadx.api.plugins.input.insns.InsnIndexType;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder;
|
||||
|
||||
public class JavaInsnInfo {
|
||||
private final int opcode;
|
||||
private final String name;
|
||||
private final int payloadSize;
|
||||
private final int regsCount;
|
||||
private final Opcode apiOpcode;
|
||||
private final InsnIndexType indexType;
|
||||
private final IJavaInsnDecoder decoder;
|
||||
|
||||
public JavaInsnInfo(int opcode, String name, int payloadSize, int regsCount, Opcode apiOpcode,
|
||||
InsnIndexType indexType, IJavaInsnDecoder decoder) {
|
||||
this.opcode = opcode;
|
||||
this.name = name;
|
||||
this.payloadSize = payloadSize;
|
||||
this.regsCount = regsCount;
|
||||
this.apiOpcode = apiOpcode;
|
||||
this.indexType = indexType;
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
public int getOpcode() {
|
||||
return opcode;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getPayloadSize() {
|
||||
return payloadSize;
|
||||
}
|
||||
|
||||
public int getRegsCount() {
|
||||
return regsCount;
|
||||
}
|
||||
|
||||
public Opcode getApiOpcode() {
|
||||
return apiOpcode;
|
||||
}
|
||||
|
||||
public InsnIndexType getIndexType() {
|
||||
return indexType;
|
||||
}
|
||||
|
||||
public IJavaInsnDecoder getDecoder() {
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "0x" + Integer.toHexString(opcode) + ": " + name;
|
||||
}
|
||||
}
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.insns.InsnIndexType;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.plugins.input.java.data.code.StackState.SVType;
|
||||
import jadx.plugins.input.java.data.code.decoders.IJavaInsnDecoder;
|
||||
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 static jadx.plugins.input.java.data.code.StackState.SVType.NARROW;
|
||||
import static jadx.plugins.input.java.data.code.StackState.SVType.WIDE;
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
public class JavaInsnsRegister {
|
||||
|
||||
private static final JavaInsnInfo[] INSN_INFO;
|
||||
|
||||
public static final long FLOAT_ZERO = Float.floatToIntBits(0.0f);
|
||||
public static final long FLOAT_ONE = Float.floatToIntBits(1.0f);
|
||||
public static final long FLOAT_TWO = Float.floatToIntBits(2.0f);
|
||||
|
||||
public static final long DOUBLE_ZERO = Double.doubleToLongBits(0.0d);
|
||||
public static final long DOUBLE_ONE = Double.doubleToLongBits(1.0d);
|
||||
|
||||
static {
|
||||
JavaInsnInfo[] arr = new JavaInsnInfo[0xCA];
|
||||
INSN_INFO = arr;
|
||||
register(arr, 0x00, "nop", 0, 0, Opcode.NOP, null);
|
||||
|
||||
constInsn(arr, 0x01, "aconst_null", Opcode.CONST, 0);
|
||||
constInsn(arr, 0x02, "iconst_m1", Opcode.CONST, -1);
|
||||
constInsn(arr, 0x03, "iconst_0", Opcode.CONST, 0);
|
||||
constInsn(arr, 0x04, "iconst_1", Opcode.CONST, 1);
|
||||
constInsn(arr, 0x05, "iconst_2", Opcode.CONST, 2);
|
||||
constInsn(arr, 0x06, "iconst_3", Opcode.CONST, 3);
|
||||
constInsn(arr, 0x07, "iconst_4", Opcode.CONST, 4);
|
||||
constInsn(arr, 0x08, "iconst_5", Opcode.CONST, 5);
|
||||
|
||||
constInsn(arr, 0x09, "lconst_0", Opcode.CONST_WIDE, 0L);
|
||||
constInsn(arr, 0x0a, "lconst_1", Opcode.CONST_WIDE, 1L);
|
||||
|
||||
constInsn(arr, 0x0b, "fconst_0", Opcode.CONST, FLOAT_ZERO);
|
||||
constInsn(arr, 0x0c, "fconst_1", Opcode.CONST, FLOAT_ONE);
|
||||
constInsn(arr, 0x0d, "fconst_2", Opcode.CONST, FLOAT_TWO);
|
||||
|
||||
constInsn(arr, 0x0e, "dconst_0", Opcode.CONST_WIDE, DOUBLE_ZERO);
|
||||
constInsn(arr, 0x0f, "dconst_1", Opcode.CONST_WIDE, DOUBLE_ONE);
|
||||
|
||||
register(arr, 0x10, "bipush", 1, 2, Opcode.CONST, s -> s.lit(s.s1()).push(0));
|
||||
register(arr, 0x11, "sipush", 2, 2, Opcode.CONST, s -> s.lit(s.s2()).push(0));
|
||||
|
||||
loadConst(arr, 0x12, "ldc", false);
|
||||
loadConst(arr, 0x13, "ldc_w", true);
|
||||
loadConst(arr, 0x14, "ldc2_w", true);
|
||||
|
||||
register(arr, 0x15, "iload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0));
|
||||
register(arr, 0x16, "lload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).pushWide(0));
|
||||
register(arr, 0x17, "fload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0));
|
||||
register(arr, 0x18, "dload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).pushWide(0));
|
||||
register(arr, 0x19, "aload", 1, 2, Opcode.MOVE, s -> s.local(1, s.u1()).push(0));
|
||||
|
||||
register(arr, 0x1a, "iload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0));
|
||||
register(arr, 0x1b, "iload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0));
|
||||
register(arr, 0x1c, "iload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0));
|
||||
register(arr, 0x1d, "iload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0));
|
||||
|
||||
register(arr, 0x1e, "lload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).pushWide(0));
|
||||
register(arr, 0x1f, "lload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).pushWide(0));
|
||||
register(arr, 0x20, "lload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).pushWide(0));
|
||||
register(arr, 0x21, "lload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).pushWide(0));
|
||||
|
||||
register(arr, 0x22, "fload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0));
|
||||
register(arr, 0x23, "fload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0));
|
||||
register(arr, 0x24, "fload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0));
|
||||
register(arr, 0x25, "fload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0));
|
||||
|
||||
register(arr, 0x26, "dload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).pushWide(0));
|
||||
register(arr, 0x27, "dload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).pushWide(0));
|
||||
register(arr, 0x28, "dload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).pushWide(0));
|
||||
register(arr, 0x29, "dload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).pushWide(0));
|
||||
|
||||
register(arr, 0x2a, "aload_0", 0, 2, Opcode.MOVE, s -> s.local(1, 0).push(0));
|
||||
register(arr, 0x2b, "aload_1", 0, 2, Opcode.MOVE, s -> s.local(1, 1).push(0));
|
||||
register(arr, 0x2c, "aload_2", 0, 2, Opcode.MOVE, s -> s.local(1, 2).push(0));
|
||||
register(arr, 0x2d, "aload_3", 0, 2, Opcode.MOVE, s -> s.local(1, 3).push(0));
|
||||
|
||||
register(arr, 0x2e, "iaload", 0, 3, Opcode.AGET, aget());
|
||||
register(arr, 0x2f, "laload", 0, 3, Opcode.AGET_WIDE, agetWide());
|
||||
register(arr, 0x30, "faload", 0, 3, Opcode.AGET, aget());
|
||||
register(arr, 0x31, "daload", 0, 3, Opcode.AGET_WIDE, agetWide());
|
||||
register(arr, 0x32, "aaload", 0, 3, Opcode.AGET_OBJECT, aget());
|
||||
register(arr, 0x33, "baload", 0, 3, Opcode.AGET_BYTE_BOOLEAN, aget());
|
||||
register(arr, 0x34, "caload", 0, 3, Opcode.AGET_CHAR, aget());
|
||||
register(arr, 0x35, "saload", 0, 3, Opcode.AGET_SHORT, aget());
|
||||
|
||||
register(arr, 0x36, "istore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1()));
|
||||
register(arr, 0x37, "lstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1()));
|
||||
register(arr, 0x38, "fstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1()));
|
||||
register(arr, 0x39, "dstore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1()));
|
||||
register(arr, 0x3a, "astore", 1, 2, Opcode.MOVE, s -> s.pop(1).local(0, s.u1()));
|
||||
|
||||
register(arr, 0x3b, "istore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0));
|
||||
register(arr, 0x3c, "istore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1));
|
||||
register(arr, 0x3d, "istore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2));
|
||||
register(arr, 0x3e, "istore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3));
|
||||
|
||||
register(arr, 0x3f, "lstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0));
|
||||
register(arr, 0x40, "lstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1));
|
||||
register(arr, 0x41, "lstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2));
|
||||
register(arr, 0x42, "lstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3));
|
||||
|
||||
register(arr, 0x43, "fstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0));
|
||||
register(arr, 0x44, "fstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1));
|
||||
register(arr, 0x45, "fstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2));
|
||||
register(arr, 0x46, "fstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3));
|
||||
|
||||
register(arr, 0x47, "dstore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0));
|
||||
register(arr, 0x48, "dstore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1));
|
||||
register(arr, 0x49, "dstore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2));
|
||||
register(arr, 0x4a, "dstore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3));
|
||||
|
||||
register(arr, 0x4b, "astore_0", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 0));
|
||||
register(arr, 0x4c, "astore_1", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 1));
|
||||
register(arr, 0x4d, "astore_2", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 2));
|
||||
register(arr, 0x4e, "astore_3", 0, 2, Opcode.MOVE, s -> s.pop(1).local(0, 3));
|
||||
|
||||
register(arr, 0x4f, "iastore", 0, 3, Opcode.APUT, aput());
|
||||
register(arr, 0x50, "lastore", 0, 3, Opcode.APUT_WIDE, aput());
|
||||
register(arr, 0x51, "fastore", 0, 3, Opcode.APUT, aput());
|
||||
register(arr, 0x52, "dastore", 0, 3, Opcode.APUT_WIDE, aput());
|
||||
register(arr, 0x53, "aastore", 0, 3, Opcode.APUT_OBJECT, aput());
|
||||
register(arr, 0x54, "bastore", 0, 3, Opcode.APUT_BYTE_BOOLEAN, aput());
|
||||
register(arr, 0x55, "castore", 0, 3, Opcode.APUT_CHAR, aput());
|
||||
register(arr, 0x56, "sastore", 0, 3, Opcode.APUT_SHORT, aput());
|
||||
|
||||
register(arr, 0x57, "pop", 0, 0, Opcode.NOP, CodeDecodeState::discard);
|
||||
register(arr, 0x58, "pop2", 0, 0, Opcode.NOP, CodeDecodeState::discardWord);
|
||||
|
||||
register(arr, 0x59, "dup", 0, 2, Opcode.MOVE, s -> s.peek(1).push(0, s.peekType(1)));
|
||||
register(arr, 0x5a, "dup_x1", 0, 6, Opcode.MOVE_MULTI,
|
||||
s -> s.push(0, s.peekType(1)).peekFrom(1, 1)
|
||||
.peekFrom(1, 2).peekFrom(2, 3)
|
||||
.peekFrom(2, 4).peekFrom(0, 5));
|
||||
register(arr, 0x5b, "dup_x2", 0, 8, Opcode.MOVE_MULTI,
|
||||
s -> s.push(0, s.peekType(1)).peekFrom(1, 1)
|
||||
.peekFrom(1, 2).peekFrom(2, 3)
|
||||
.peekFrom(2, 4).peekFrom(3, 5)
|
||||
.peekFrom(3, 6).peekFrom(0, 7));
|
||||
register(arr, 0x5c, "dup2", 0, 4, Opcode.MOVE_MULTI, s -> {
|
||||
if (s.peekType(0) == NARROW) {
|
||||
s.peekFrom(0, 3).peekFrom(1, 1).push(0, NARROW).push(2, NARROW);
|
||||
} else {
|
||||
s.peek(1).push(0, s.peekType(1));
|
||||
}
|
||||
});
|
||||
register(arr, 0x5d, "dup2_x1", 0, 10, Opcode.MOVE_MULTI,
|
||||
s -> {
|
||||
if (s.peekType(0) == NARROW) {
|
||||
s.push(0, NARROW).peekFrom(2, 1)
|
||||
.push(2, NARROW).peekFrom(2, 3)
|
||||
.peekFrom(2, 4).peekFrom(4, 5)
|
||||
.peekFrom(3, 6).peekFrom(0, 7)
|
||||
.peekFrom(4, 8).peekFrom(1, 9);
|
||||
} else {
|
||||
s.insn().setRegsCount(6);
|
||||
s.push(0, WIDE).peekFrom(1, 1)
|
||||
.peekFrom(1, 2).peekFrom(2, 3)
|
||||
.peekFrom(2, 4).peekFrom(0, 5);
|
||||
}
|
||||
});
|
||||
|
||||
register(arr, 0x60, "iadd", 0, 3, Opcode.ADD_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x61, "ladd", 0, 3, Opcode.ADD_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x62, "fadd", 0, 3, Opcode.ADD_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x63, "dadd", 0, 3, Opcode.ADD_DOUBLE, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x64, "isub", 0, 3, Opcode.SUB_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x65, "lsub", 0, 3, Opcode.SUB_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x66, "fsub", 0, 3, Opcode.SUB_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x67, "dsub", 0, 3, Opcode.SUB_DOUBLE, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x68, "imul", 0, 3, Opcode.MUL_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x69, "lmul", 0, 3, Opcode.MUL_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x6a, "fmul", 0, 3, Opcode.MUL_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x6b, "dmul", 0, 3, Opcode.MUL_DOUBLE, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x6c, "idiv", 0, 3, Opcode.DIV_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x6d, "ldiv", 0, 3, Opcode.DIV_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x6e, "fdiv", 0, 3, Opcode.DIV_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x6f, "ddiv", 0, 3, Opcode.DIV_DOUBLE, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x70, "irem", 0, 3, Opcode.REM_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x71, "lrem", 0, 3, Opcode.REM_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x72, "frem", 0, 3, Opcode.REM_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x73, "drem", 0, 3, Opcode.REM_DOUBLE, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x74, "ineg", 0, 2, Opcode.NEG_INT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x75, "lneg", 0, 2, Opcode.NEG_LONG, oneRegWithResult(WIDE));
|
||||
register(arr, 0x76, "fneg", 0, 2, Opcode.NEG_FLOAT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x77, "dneg", 0, 2, Opcode.NEG_DOUBLE, oneRegWithResult(WIDE));
|
||||
|
||||
register(arr, 0x78, "ishl", 0, 3, Opcode.SHL_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x79, "lshl", 0, 3, Opcode.SHL_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x7a, "ishr", 0, 3, Opcode.SHR_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x7b, "lshr", 0, 3, Opcode.SHR_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x7c, "iushr", 0, 3, Opcode.USHR_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x7d, "lushr", 0, 3, Opcode.USHR_LONG, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x7e, "iand", 0, 3, Opcode.AND_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x7f, "land", 0, 3, Opcode.AND_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x80, "ior", 0, 3, Opcode.OR_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x81, "lor", 0, 3, Opcode.OR_LONG, twoRegsWithResult(WIDE));
|
||||
register(arr, 0x82, "ixor", 0, 3, Opcode.XOR_INT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x83, "lxor", 0, 3, Opcode.XOR_LONG, twoRegsWithResult(WIDE));
|
||||
|
||||
register(arr, 0x84, "iinc", 2, 2, Opcode.ADD_INT_LIT, s -> {
|
||||
int varNum = s.u1();
|
||||
s.local(0, varNum).local(1, varNum).lit(s.reader().readS1());
|
||||
});
|
||||
|
||||
register(arr, 0x85, "i2l", 0, 2, Opcode.INT_TO_LONG, oneRegWithResult(WIDE));
|
||||
register(arr, 0x86, "i2f", 0, 2, Opcode.INT_TO_FLOAT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x87, "i2d", 0, 2, Opcode.INT_TO_DOUBLE, oneRegWithResult(WIDE));
|
||||
register(arr, 0x88, "l2i", 0, 2, Opcode.LONG_TO_INT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x89, "l2f", 0, 2, Opcode.LONG_TO_FLOAT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x8a, "l2d", 0, 2, Opcode.LONG_TO_DOUBLE, oneRegWithResult(WIDE));
|
||||
register(arr, 0x8b, "f2i", 0, 2, Opcode.FLOAT_TO_INT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x8c, "f2l", 0, 2, Opcode.FLOAT_TO_LONG, oneRegWithResult(WIDE));
|
||||
register(arr, 0x8d, "f2d", 0, 2, Opcode.FLOAT_TO_DOUBLE, oneRegWithResult(WIDE));
|
||||
register(arr, 0x8e, "d2i", 0, 2, Opcode.DOUBLE_TO_INT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x8f, "d2l", 0, 2, Opcode.DOUBLE_TO_LONG, oneRegWithResult(WIDE));
|
||||
register(arr, 0x90, "d2f", 0, 2, Opcode.DOUBLE_TO_FLOAT, oneRegWithResult(NARROW));
|
||||
register(arr, 0x91, "i2b", 0, 2, Opcode.INT_TO_BYTE, oneRegWithResult(NARROW));
|
||||
register(arr, 0x92, "i2c", 0, 2, Opcode.INT_TO_CHAR, oneRegWithResult(NARROW));
|
||||
register(arr, 0x93, "i2s", 0, 2, Opcode.INT_TO_SHORT, oneRegWithResult(NARROW));
|
||||
|
||||
register(arr, 0x94, "lcmp", 0, 3, Opcode.CMP_LONG, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x95, "fcmpl", 0, 3, Opcode.CMPL_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x96, "fcmpg", 0, 3, Opcode.CMPG_FLOAT, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x97, "dcmpl", 0, 3, Opcode.CMPL_DOUBLE, twoRegsWithResult(NARROW));
|
||||
register(arr, 0x98, "dcmpg", 0, 3, Opcode.CMPG_DOUBLE, twoRegsWithResult(NARROW));
|
||||
|
||||
register(arr, 0x99, "ifeq", 2, 1, Opcode.IF_EQZ, zeroCmp());
|
||||
register(arr, 0x9a, "ifne", 2, 1, Opcode.IF_NEZ, zeroCmp());
|
||||
register(arr, 0x9b, "iflt", 2, 1, Opcode.IF_LTZ, zeroCmp());
|
||||
register(arr, 0x9c, "ifge", 2, 1, Opcode.IF_GEZ, zeroCmp());
|
||||
register(arr, 0x9d, "ifgt", 2, 1, Opcode.IF_GTZ, zeroCmp());
|
||||
register(arr, 0x9e, "ifle", 2, 1, Opcode.IF_LEZ, zeroCmp());
|
||||
|
||||
register(arr, 0x9f, "if_icmpeq", 2, 2, Opcode.IF_EQ, cmp());
|
||||
register(arr, 0xa0, "if_icmpne", 2, 2, Opcode.IF_NE, cmp());
|
||||
register(arr, 0xa1, "if_icmplt", 2, 2, Opcode.IF_LT, cmp());
|
||||
register(arr, 0xa2, "if_icmpge", 2, 2, Opcode.IF_GE, cmp());
|
||||
register(arr, 0xa3, "if_icmpgt", 2, 2, Opcode.IF_GT, cmp());
|
||||
register(arr, 0xa4, "if_icmple", 2, 2, Opcode.IF_LE, cmp());
|
||||
register(arr, 0xa5, "if_acmpeq", 2, 2, Opcode.IF_EQ, cmp());
|
||||
register(arr, 0xa6, "if_acmpne", 2, 2, Opcode.IF_NE, cmp());
|
||||
|
||||
register(arr, 0xa7, "goto", 2, 0, Opcode.GOTO, s -> s.jump(s.s2()));
|
||||
|
||||
register(arr, 0xaa, "tableswitch", -1, 1, Opcode.PACKED_SWITCH, new TableSwitchDecoder());
|
||||
register(arr, 0xab, "lookupswitch", -1, 1, Opcode.SPARSE_SWITCH, new LookupSwitchDecoder());
|
||||
|
||||
register(arr, 0xac, "ireturn", 0, 1, Opcode.RETURN, s -> s.pop(0));
|
||||
register(arr, 0xad, "lreturn", 0, 1, Opcode.RETURN, s -> s.pop(0));
|
||||
register(arr, 0xae, "freturn", 0, 1, Opcode.RETURN, s -> s.pop(0));
|
||||
register(arr, 0xaf, "dreturn", 0, 1, Opcode.RETURN, s -> s.pop(0));
|
||||
register(arr, 0xb0, "areturn", 0, 1, Opcode.RETURN, s -> s.pop(0));
|
||||
register(arr, 0xb1, "return", 0, 0, Opcode.RETURN_VOID, null);
|
||||
|
||||
register(arr, 0xb2, "getstatic", 2, 1, Opcode.SGET, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).push(0, s.fieldType()));
|
||||
register(arr, 0xb3, "putstatic", 2, 1, Opcode.SPUT, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(0));
|
||||
register(arr, 0xb4, "getfield", 2, 2, Opcode.IGET, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(1).push(0, s.fieldType()));
|
||||
register(arr, 0xb5, "putfield", 2, 2, Opcode.IPUT, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(0).pop(1));
|
||||
|
||||
invoke(arr, 0xb6, "invokevirtual", 2, Opcode.INVOKE_VIRTUAL);
|
||||
invoke(arr, 0xb7, "invokespecial", 2, Opcode.INVOKE_DIRECT);
|
||||
invoke(arr, 0xb8, "invokestatic", 2, Opcode.INVOKE_STATIC);
|
||||
invoke(arr, 0xb9, "invokeinterface", 4, Opcode.INVOKE_INTERFACE);
|
||||
invoke(arr, 0xba, "invokedynamic", 4, Opcode.INVOKE_CUSTOM);
|
||||
|
||||
register(arr, 0xbb, "new", 2, 1, Opcode.NEW_INSTANCE, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).push(0));
|
||||
register(arr, 0xbc, "newarray", 1, 2, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, s -> s.idx(s.u1()).pop(1).push(0).lit(1));
|
||||
register(arr, 0xbd, "anewarray", 2, 2, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0).lit(1));
|
||||
register(arr, 0xbe, "arraylength", 0, 2, Opcode.ARRAY_LENGTH, oneRegWithResult(NARROW));
|
||||
register(arr, 0xbf, "athrow", 0, 1, Opcode.THROW, s -> s.pop(0).clear());
|
||||
|
||||
register(arr, 0xc0, "checkcast", 2, 2, Opcode.CHECK_CAST, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0));
|
||||
register(arr, 0xc1, "instanceof", 2, 2, Opcode.INSTANCE_OF, InsnIndexType.TYPE_REF, s -> s.idx(s.u2()).pop(1).push(0));
|
||||
|
||||
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, 0xc5, "multianewarray", 3, -1, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, newArrayMulti());
|
||||
register(arr, 0xc6, "ifnull", 2, 1, Opcode.IF_EQZ, zeroCmp());
|
||||
register(arr, 0xc7, "ifnonnull", 2, 1, Opcode.IF_NEZ, zeroCmp());
|
||||
|
||||
register(arr, 0xc8, "goto_w", 4, 0, Opcode.GOTO, s -> s.jump(s.reader().readS4()));
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder newArrayMulti() {
|
||||
return s -> {
|
||||
s.idx(s.u2());
|
||||
int dim = s.u1();
|
||||
JavaInsnData insn = s.insn();
|
||||
insn.setLiteral(dim);
|
||||
insn.setRegsCount(dim + 1);
|
||||
for (int i = dim; i > 0; i--) {
|
||||
s.pop(i);
|
||||
}
|
||||
s.push(0);
|
||||
};
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder oneRegWithResult(SVType type) {
|
||||
return s -> s.pop(1).push(0, type);
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder twoRegsWithResult(SVType type) {
|
||||
return s -> s.pop(2).pop(1).push(0, type);
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder aget() {
|
||||
return s -> s.pop(2).pop(1).push(0);
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder agetWide() {
|
||||
return s -> s.pop(2).pop(1).pushWide(0);
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder aput() {
|
||||
return s -> s.pop(0).pop(2).pop(1);
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder zeroCmp() {
|
||||
return s -> s.pop(0).jump(s.s2());
|
||||
}
|
||||
|
||||
private static IJavaInsnDecoder cmp() {
|
||||
return s -> s.pop(1).pop(0).jump(s.s2());
|
||||
}
|
||||
|
||||
private static void invoke(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, Opcode apiOpcode) {
|
||||
InsnIndexType indexType = apiOpcode == Opcode.INVOKE_CUSTOM ? InsnIndexType.CALL_SITE : InsnIndexType.METHOD_REF;
|
||||
register(arr, opcode, name, payloadSize, -1, apiOpcode, indexType, new InvokeDecoder(payloadSize, apiOpcode));
|
||||
}
|
||||
|
||||
private static void constInsn(JavaInsnInfo[] arr, int opcode, String name, Opcode apiOpcode, long literal) {
|
||||
register(arr, opcode, name, 0, 1, apiOpcode, InsnIndexType.NONE, state -> {
|
||||
state.insn().setLiteral(literal);
|
||||
state.push(0, apiOpcode == Opcode.CONST_WIDE ? SVType.WIDE : NARROW);
|
||||
});
|
||||
}
|
||||
|
||||
private static void loadConst(JavaInsnInfo[] arr, int opcode, String name, boolean wide) {
|
||||
register(arr, opcode, name, wide ? 2 : 1, 2, Opcode.CONST, InsnIndexType.NONE, new LoadConstDecoder(wide));
|
||||
}
|
||||
|
||||
private static void register(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, int regsCount,
|
||||
Opcode apiOpcode, IJavaInsnDecoder decoder) {
|
||||
register(arr, opcode, name, payloadSize, regsCount, apiOpcode, InsnIndexType.NONE, decoder);
|
||||
}
|
||||
|
||||
private static void register(JavaInsnInfo[] arr, int opcode, String name, int payloadSize, int regsCount,
|
||||
Opcode apiOpcode, InsnIndexType indexType, IJavaInsnDecoder decoder) {
|
||||
if (arr[opcode] != null) {
|
||||
throw new IllegalStateException("Duplicate opcode init: 0x" + Integer.toHexString(opcode));
|
||||
}
|
||||
arr[opcode] = new JavaInsnInfo(opcode, name, payloadSize, regsCount, apiOpcode, indexType, decoder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaInsnInfo get(int opcode) {
|
||||
return INSN_INFO[opcode];
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package jadx.plugins.input.java.data.code;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class StackState {
|
||||
|
||||
/**
|
||||
* Stack value type
|
||||
*/
|
||||
public enum SVType {
|
||||
NARROW, // int, float, etc
|
||||
WIDE, // long, double
|
||||
}
|
||||
|
||||
private int pos = -1;
|
||||
private final SVType[] stack;
|
||||
|
||||
public StackState(int maxStack) {
|
||||
this.stack = new SVType[maxStack];
|
||||
}
|
||||
|
||||
private StackState(int pos, SVType[] stack) {
|
||||
this.pos = pos;
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
public StackState copy() {
|
||||
return new StackState(pos, Arrays.copyOf(stack, stack.length));
|
||||
}
|
||||
|
||||
public int peek() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int peekAt(int at) {
|
||||
return pos - at;
|
||||
}
|
||||
|
||||
public SVType peekTypeAt(int at) {
|
||||
int p = pos - at;
|
||||
if (checkStackIndex(p)) {
|
||||
return stack[p];
|
||||
}
|
||||
return SVType.NARROW;
|
||||
}
|
||||
|
||||
public int push(SVType type) {
|
||||
int p = ++pos;
|
||||
if (checkStackIndex(p)) {
|
||||
stack[p] = type;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private boolean checkStackIndex(int p) {
|
||||
return p >= 0 && p < stack.length;
|
||||
}
|
||||
|
||||
public int pop() {
|
||||
return pos--;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
pos = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
int size = pos + 1;
|
||||
String arr;
|
||||
if (size == 0) {
|
||||
arr = "empty";
|
||||
} else if (size > 0 && size < stack.length) {
|
||||
arr = Arrays.toString(Arrays.copyOf(stack, size));
|
||||
} else {
|
||||
arr = Arrays.toString(stack) + " (max)";
|
||||
}
|
||||
return "Stack: " + size + ": " + arr;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.plugins.input.java.data.code.CodeDecodeState;
|
||||
|
||||
public interface IJavaInsnDecoder {
|
||||
void decode(CodeDecodeState state);
|
||||
|
||||
default void skip(CodeDecodeState state) {
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.api.plugins.input.data.ICallSite;
|
||||
import jadx.api.plugins.input.data.IMethodProto;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
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;
|
||||
|
||||
public class InvokeDecoder implements IJavaInsnDecoder {
|
||||
private final int payloadSize;
|
||||
private final Opcode apiOpcode;
|
||||
|
||||
public InvokeDecoder(int payloadSize, Opcode apiOpcode) {
|
||||
this.payloadSize = payloadSize;
|
||||
this.apiOpcode = apiOpcode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(CodeDecodeState state) {
|
||||
DataReader reader = state.reader();
|
||||
int mthIdx = reader.readS2();
|
||||
if (payloadSize == 4) {
|
||||
reader.skip(2);
|
||||
}
|
||||
JavaInsnData insn = state.insn();
|
||||
insn.setIndex(mthIdx);
|
||||
boolean instanceCall;
|
||||
IMethodProto mthProto;
|
||||
if (apiOpcode == Opcode.INVOKE_CUSTOM) {
|
||||
ICallSite callSite = insn.getIndexAsCallSite();
|
||||
insn.setPayload(callSite);
|
||||
mthProto = (IMethodProto) callSite.getValues().get(2).getValue();
|
||||
instanceCall = false; // 'this' arg already included in proto args
|
||||
} else {
|
||||
IMethodRef mthRef = insn.getIndexAsMethod();
|
||||
mthRef.load();
|
||||
insn.setPayload(mthRef);
|
||||
mthProto = mthRef;
|
||||
instanceCall = apiOpcode != Opcode.INVOKE_STATIC;
|
||||
}
|
||||
|
||||
int argsCount = mthProto.getArgTypes().size();
|
||||
if (instanceCall) {
|
||||
argsCount++;
|
||||
}
|
||||
insn.setRegsCount(argsCount * 2); // allocate twice of the size for worst case
|
||||
int[] regs = insn.getRegsArray();
|
||||
|
||||
// calculate actual count of registers
|
||||
// set '1' in regs to be filled with stack values later, '0' for skip
|
||||
int regsCount = 0;
|
||||
if (instanceCall) {
|
||||
regs[regsCount++] = 1;
|
||||
}
|
||||
for (String type : mthProto.getArgTypes()) {
|
||||
int size = getRegsCountForType(type);
|
||||
regs[regsCount++] = 1;
|
||||
if (size == 2) {
|
||||
regs[regsCount++] = 0;
|
||||
}
|
||||
}
|
||||
insn.setRegsCount(regsCount);
|
||||
for (int i = regsCount - 1; i >= 0; i--) {
|
||||
if (regs[i] == 1) {
|
||||
state.pop(i);
|
||||
}
|
||||
}
|
||||
String returnType = mthProto.getReturnType();
|
||||
if (!returnType.equals("V")) {
|
||||
insn.setResultReg(state.push(returnType));
|
||||
} else {
|
||||
insn.setResultReg(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRegsCountForType(String type) {
|
||||
char c = type.charAt(0);
|
||||
if (c == 'J' || c == 'D') {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.plugins.input.java.data.ConstPoolReader;
|
||||
import jadx.plugins.input.java.data.ConstantType;
|
||||
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.data.code.StackState.SVType;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class LoadConstDecoder implements IJavaInsnDecoder {
|
||||
private final boolean wide;
|
||||
|
||||
public LoadConstDecoder(boolean wide) {
|
||||
this.wide = wide;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(CodeDecodeState state) {
|
||||
DataReader reader = state.reader();
|
||||
JavaInsnData insn = state.insn();
|
||||
int index;
|
||||
if (wide) {
|
||||
index = reader.readU2();
|
||||
} else {
|
||||
index = reader.readU1();
|
||||
}
|
||||
ConstPoolReader constPoolReader = insn.constPoolReader();
|
||||
ConstantType constType = constPoolReader.jumpToConst(index);
|
||||
switch (constType) {
|
||||
case INTEGER:
|
||||
case FLOAT:
|
||||
insn.setLiteral(constPoolReader.readU4());
|
||||
insn.setOpcode(Opcode.CONST);
|
||||
state.push(0, SVType.NARROW);
|
||||
break;
|
||||
|
||||
case LONG:
|
||||
case DOUBLE:
|
||||
insn.setLiteral(constPoolReader.readU8());
|
||||
insn.setOpcode(Opcode.CONST_WIDE);
|
||||
state.push(0, SVType.WIDE);
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
insn.setIndex(constPoolReader.readU2());
|
||||
insn.setOpcode(Opcode.CONST_STRING);
|
||||
state.push(0, SVType.NARROW);
|
||||
break;
|
||||
|
||||
case UTF8:
|
||||
insn.setIndex(index);
|
||||
insn.setOpcode(Opcode.CONST_STRING);
|
||||
state.push(0, SVType.NARROW);
|
||||
break;
|
||||
|
||||
case CLASS:
|
||||
insn.setIndex(index);
|
||||
insn.setOpcode(Opcode.CONST_CLASS);
|
||||
state.push(0, SVType.NARROW);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Unsupported constant type: " + constType);
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.api.plugins.input.insns.custom.impl.SwitchPayload;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.code.CodeDecodeState;
|
||||
import jadx.plugins.input.java.data.code.JavaInsnData;
|
||||
|
||||
public class LookupSwitchDecoder implements IJavaInsnDecoder {
|
||||
|
||||
@Override
|
||||
public void decode(CodeDecodeState state) {
|
||||
read(state, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(CodeDecodeState state) {
|
||||
read(state, true);
|
||||
}
|
||||
|
||||
private static void read(CodeDecodeState state, boolean skip) {
|
||||
DataReader reader = state.reader();
|
||||
JavaInsnData insn = state.insn();
|
||||
int dataOffset = reader.getOffset();
|
||||
int insnOffset = insn.getOffset();
|
||||
reader.skip(3 - insnOffset % 4);
|
||||
int defTarget = insnOffset + reader.readS4();
|
||||
int pairs = reader.readS4();
|
||||
if (skip) {
|
||||
reader.skip(pairs * 8);
|
||||
} else {
|
||||
state.pop(0);
|
||||
int[] keys = new int[pairs];
|
||||
int[] targets = new int[pairs];
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
keys[i] = reader.readS4();
|
||||
int target = insnOffset + reader.readS4();
|
||||
targets[i] = target;
|
||||
state.registerJump(target);
|
||||
}
|
||||
insn.setTarget(defTarget);
|
||||
state.registerJump(defTarget);
|
||||
insn.setPayload(new SwitchPayload(pairs, keys, targets));
|
||||
}
|
||||
insn.setPayloadSize(reader.getOffset() - dataOffset);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package jadx.plugins.input.java.data.code.decoders;
|
||||
|
||||
import jadx.api.plugins.input.insns.custom.impl.SwitchPayload;
|
||||
import jadx.plugins.input.java.data.DataReader;
|
||||
import jadx.plugins.input.java.data.code.CodeDecodeState;
|
||||
import jadx.plugins.input.java.data.code.JavaInsnData;
|
||||
|
||||
public class TableSwitchDecoder implements IJavaInsnDecoder {
|
||||
|
||||
@Override
|
||||
public void decode(CodeDecodeState state) {
|
||||
read(state, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(CodeDecodeState state) {
|
||||
read(state, true);
|
||||
}
|
||||
|
||||
private static void read(CodeDecodeState state, boolean skip) {
|
||||
DataReader reader = state.reader();
|
||||
JavaInsnData insn = state.insn();
|
||||
int dataOffset = reader.getOffset();
|
||||
int insnOffset = insn.getOffset();
|
||||
reader.skip(3 - insnOffset % 4);
|
||||
int defTarget = insnOffset + reader.readS4();
|
||||
int low = reader.readS4();
|
||||
int high = reader.readS4();
|
||||
int count = high - low + 1;
|
||||
if (skip) {
|
||||
reader.skip(count * 4);
|
||||
} else {
|
||||
state.pop(0);
|
||||
int[] keys = new int[count];
|
||||
int[] targets = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
int target = insnOffset + reader.readS4();
|
||||
keys[i] = low + i;
|
||||
targets[i] = target;
|
||||
state.registerJump(target);
|
||||
}
|
||||
insn.setTarget(defTarget);
|
||||
state.registerJump(defTarget);
|
||||
insn.setPayload(new SwitchPayload(count, keys, targets));
|
||||
}
|
||||
insn.setPayloadSize(reader.getOffset() - dataOffset);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package jadx.plugins.input.java.data.code.trycatch;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JavaSingleCatch {
|
||||
private final int handler;
|
||||
private final @Nullable String type;
|
||||
|
||||
public JavaSingleCatch(int handler, @Nullable String type) {
|
||||
this.handler = handler;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public @Nullable String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package jadx.plugins.input.java.data.code.trycatch;
|
||||
|
||||
import jadx.api.plugins.input.data.ICatch;
|
||||
import jadx.api.plugins.input.data.ITry;
|
||||
|
||||
public class JavaTryData implements ITry {
|
||||
|
||||
private final int startAddr;
|
||||
private final int endAddr;
|
||||
private ICatch catchHandler;
|
||||
|
||||
public JavaTryData(int startAddr, int endAddr) {
|
||||
this.startAddr = startAddr;
|
||||
this.endAddr = endAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICatch getCatch() {
|
||||
return catchHandler;
|
||||
}
|
||||
|
||||
public void setCatch(ICatch catchHandler) {
|
||||
this.catchHandler = catchHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartAddress() {
|
||||
return startAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndAddress() {
|
||||
return endAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return startAddr + 31 * endAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JavaTryData)) {
|
||||
return false;
|
||||
}
|
||||
JavaTryData that = (JavaTryData) o;
|
||||
return startAddr == that.startAddr && endAddr == that.endAddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Try{" + startAddr + " - " + endAddr + ": " + catchHandler + '}';
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package jadx.plugins.input.java.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.plugins.input.java.data.JavaMethodProto;
|
||||
|
||||
public class DescriptorParser {
|
||||
|
||||
public static void fillMethodProto(String mthDesc, JavaMethodProto mthProto) {
|
||||
new DescriptorParser(mthDesc).parseMethodDescriptor(mthProto);
|
||||
}
|
||||
|
||||
public static JavaMethodProto parseToMethodProto(String mthDesc) {
|
||||
JavaMethodProto mthProto = new JavaMethodProto();
|
||||
new DescriptorParser(mthDesc).parseMethodDescriptor(mthProto);
|
||||
return mthProto;
|
||||
}
|
||||
|
||||
private final String desc;
|
||||
private int pos;
|
||||
|
||||
private DescriptorParser(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
private void parseMethodDescriptor(JavaMethodProto mthProto) {
|
||||
validate('(');
|
||||
if (check(')')) {
|
||||
mthProto.setArgTypes(Collections.emptyList());
|
||||
} else {
|
||||
mthProto.setArgTypes(readArgsList());
|
||||
}
|
||||
validate(')');
|
||||
mthProto.setReturnType(readType());
|
||||
}
|
||||
|
||||
private List<String> readArgsList() {
|
||||
List<String> list = new ArrayList<>(5);
|
||||
do {
|
||||
list.add(readType());
|
||||
} while (!check(')'));
|
||||
return list;
|
||||
}
|
||||
|
||||
private String readType() {
|
||||
int cur = pos;
|
||||
if (cur >= desc.length()) {
|
||||
return null;
|
||||
}
|
||||
char ch = desc.charAt(cur);
|
||||
switch (ch) {
|
||||
case 'L':
|
||||
int end = desc.indexOf(';', cur);
|
||||
if (end == -1) {
|
||||
throw new JavaClassParseException("Unexpected object type descriptor: " + desc);
|
||||
}
|
||||
int lastChar = end + 1;
|
||||
String type = desc.substring(cur, lastChar);
|
||||
pos = lastChar;
|
||||
return type;
|
||||
|
||||
case '[':
|
||||
pos++;
|
||||
return "[" + readType();
|
||||
|
||||
default:
|
||||
String primitiveType = parsePrimitiveType(ch);
|
||||
pos = cur + 1;
|
||||
return primitiveType;
|
||||
}
|
||||
}
|
||||
|
||||
public String parsePrimitiveType(char f) {
|
||||
switch (f) {
|
||||
case 'Z':
|
||||
return "Z";
|
||||
case 'B':
|
||||
return "B";
|
||||
case 'C':
|
||||
return "C";
|
||||
case 'S':
|
||||
return "S";
|
||||
case 'I':
|
||||
return "I";
|
||||
case 'J':
|
||||
return "J";
|
||||
case 'F':
|
||||
return "F";
|
||||
case 'D':
|
||||
return "D";
|
||||
case 'V':
|
||||
return "V";
|
||||
|
||||
default:
|
||||
throw new JavaClassParseException("Unexpected char '" + f + "' in descriptor " + desc);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean check(char exp) {
|
||||
return desc.charAt(pos) == exp;
|
||||
}
|
||||
|
||||
private void validate(char exp) {
|
||||
if (!check(exp)) {
|
||||
throw new JavaClassParseException("Unexpected char in descriptor: " + desc + " at pos " + pos + ", expected: " + exp);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package jadx.plugins.input.java.utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DisasmUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DisasmUtils.class);
|
||||
|
||||
/**
|
||||
* Use javap as a temporary disassembler for java bytecode
|
||||
*/
|
||||
public static String get(byte[] bytes) {
|
||||
try {
|
||||
Path tmpCls = null;
|
||||
try {
|
||||
tmpCls = Files.createTempFile("jadx", ".class");
|
||||
Files.write(tmpCls, bytes, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
Process process = Runtime.getRuntime().exec(new String[] {
|
||||
"javap", "-constants", "-v", "-p", "-c",
|
||||
tmpCls.toAbsolutePath().toString()
|
||||
});
|
||||
process.waitFor(2, TimeUnit.SECONDS);
|
||||
return inputStreamToString(process.getInputStream());
|
||||
} finally {
|
||||
if (tmpCls != null) {
|
||||
Files.delete(tmpCls);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Java class disasm error", e);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
public static String inputStreamToString(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[8 * 1024];
|
||||
while (true) {
|
||||
int r = in.read(buf);
|
||||
if (r == -1) {
|
||||
break;
|
||||
}
|
||||
out.write(buf, 0, r);
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package jadx.plugins.input.java.utils;
|
||||
|
||||
public class JavaClassParseException extends RuntimeException {
|
||||
private static final long serialVersionUID = -8452845601753645491L;
|
||||
|
||||
public JavaClassParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public JavaClassParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
jadx.plugins.input.java.JavaInputPlugin
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package jadx.plugins.input.java.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.plugins.input.java.data.JavaMethodRef;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
class DescriptorParserTest {
|
||||
|
||||
@Test
|
||||
public void testPrimitives() {
|
||||
check("()V", "V");
|
||||
check("(I)D", "D", "I");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjects() {
|
||||
check("(Ljava/lang/String;Ljava/lang/Object;)V", "V", "Ljava/lang/String;", "Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
@SuppressWarnings("CatchMayIgnoreException")
|
||||
private void check(String desc, String retType, String... argTypes) {
|
||||
JavaMethodRef mthRef = new JavaMethodRef();
|
||||
try {
|
||||
DescriptorParser.fillMethodProto(desc, mthRef);
|
||||
} catch (Exception e) {
|
||||
fail("Parse failed for: " + desc, e);
|
||||
}
|
||||
|
||||
assertThat(mthRef.getReturnType()).isEqualTo(retType);
|
||||
assertThat(mthRef.getArgTypes()).isEqualTo(Arrays.asList(argTypes));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user