feat: input plugin for java bytecode

This commit is contained in:
Skylot
2021-08-01 17:50:39 +01:00
parent 2d9bcdb87a
commit 1efdcd7b10
242 changed files with 5988 additions and 1174 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
});
}
}
@@ -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);
}
}
}
@@ -0,0 +1,4 @@
package jadx.plugins.input.java.data.attributes;
public interface IJavaAttribute {
}
@@ -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);
}
@@ -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() + '}';
}
}
@@ -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;
}
}
@@ -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 : "");
}
}
@@ -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);
};
}
}
@@ -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);
};
}
}
@@ -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);
};
}
}
@@ -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());
}
}
@@ -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()));
}
}
@@ -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 {
}
@@ -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);
}
}
@@ -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()));
}
}
@@ -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);
};
}
}
@@ -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()));
}
}
@@ -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);
};
}
}
@@ -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;
}
}
@@ -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()));
}
}
@@ -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()));
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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];
}
}
@@ -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;
}
}
@@ -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) {
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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 + '}';
}
}
@@ -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++;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -0,0 +1 @@
jadx.plugins.input.java.JavaInputPlugin