未验证 提交 9ea3f0f2 编写于 作者: S Skylot

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

上级 868fa900
......@@ -6,10 +6,13 @@ dependencies {
api(project(':jadx-plugins:jadx-plugins-api'))
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
implementation 'com.android.tools.build:aapt2-proto:4.2.1-7147631'
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
testImplementation 'org.ow2.asm:asm:9.2'
testImplementation 'org.ow2.asm:asm-util:9.2'
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
......
......@@ -586,7 +586,6 @@ public class InsnGen {
case MOVE_MULTI:
fallbackOnlyInsn(insn);
code.add("move-multi: ");
int len = insn.getArgsCount();
for (int i = 0; i < len - 1; i += 2) {
addArg(code, insn.getArg(i));
......
......@@ -147,6 +147,10 @@ public class AccessInfo {
return (accFlags & AccessFlags.VOLATILE) != 0;
}
public boolean isModuleInfo() {
return (accFlags & AccessFlags.MODULE) != 0;
}
public AFType getType() {
return type;
}
......@@ -200,6 +204,9 @@ public class AccessInfo {
if ((accFlags & AccessFlags.STRICT) != 0) {
code.append("strict ");
}
if (isModuleInfo()) {
code.append("/* module-info */ ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.SUPER) != 0) {
code.append("/* super */ ");
......
......@@ -20,6 +20,8 @@ import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultAttr;
......@@ -27,6 +29,7 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationDefaultClassAttr;
import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr;
import jadx.api.plugins.input.data.attributes.types.InnerClsInfo;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.api.plugins.input.data.impl.ListConsumer;
import jadx.core.Consts;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
......@@ -97,34 +100,46 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
private void initialLoad(IClassData cls) {
try {
String superType = cls.getSuperType();
if (superType == null) {
// only java.lang.Object don't have super class
if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
}
this.superClass = null;
} else {
this.superClass = ArgType.object(superType);
}
addAttrs(cls.getAttributes());
this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
this.superClass = checkSuperType(cls);
this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
methods = new ArrayList<>();
fields = new ArrayList<>();
cls.visitFieldsAndMethods(
fld -> fields.add(FieldNode.build(this, fld)),
mth -> methods.add(MethodNode.build(this, mth)));
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
this.fields = fieldsConsumer.getResult();
this.methods = methodsConsumer.getResult();
addAttrs(cls.getAttributes());
accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
initStaticValues(fields);
processAttributes(this);
buildCache();
// TODO: implement module attribute parsing
if (this.accessFlags.isModuleInfo()) {
this.addWarnComment("Modules not supported yet");
}
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
}
}
private ArgType checkSuperType(IClassData cls) {
String superType = cls.getSuperType();
if (superType == null) {
if (clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
// java.lang.Object don't have super class
return null;
}
if (this.accessFlags.isModuleInfo()) {
// module-info also don't have super class
return null;
}
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
}
return ArgType.object(superType);
}
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
this.superClass = superClass;
this.interfaces = interfaces;
......
......@@ -97,14 +97,7 @@ public class RootNode {
}
if (classes.size() != clsMap.size()) {
// class name duplication detected
classes.stream().collect(Collectors.groupingBy(ClassNode::getClassInfo))
.entrySet().stream()
.filter(entry -> entry.getValue().size() > 1)
.forEach(entry -> {
LOG.warn("Found duplicated class: {}, count: {}. Only one will be loaded!", entry.getKey(),
entry.getValue().size());
entry.getValue().forEach(cls -> cls.addAttr(AType.COMMENTS, "WARNING: Classes with same name are omitted"));
});
markDuplicatedClasses(classes);
}
classes = new ArrayList<>(clsMap.values());
// sort classes by name, expect top classes before inner
......@@ -135,6 +128,27 @@ public class RootNode {
ErrorsCounter.error(clsNode, "Load error", exc);
}
private static void markDuplicatedClasses(List<ClassNode> classes) {
classes.stream()
.collect(Collectors.groupingBy(ClassNode::getClassInfo))
.entrySet()
.stream()
.filter(entry -> entry.getValue().size() > 1)
.forEach(entry -> {
List<String> sources = Utils.collectionMap(entry.getValue(), ClassNode::getInputFileName);
LOG.warn("Found duplicated class: {}, count: {}. Only one will be loaded!\n {}",
entry.getKey(), entry.getValue().size(), String.join("\n ", sources));
entry.getValue().forEach(cls -> {
String thisSource = cls.getInputFileName();
String otherSourceStr = sources.stream()
.filter(s -> !s.equals(thisSource))
.sorted()
.collect(Collectors.joining("\n "));
cls.addAttr(AType.COMMENTS, "WARNING: Classes with same name are omitted:\n " + otherSourceStr + '\n');
});
});
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
......
......@@ -41,15 +41,15 @@ public abstract class BaseExternalTest extends IntegrationTest {
return args;
}
protected void decompile(JadxArgs jadxArgs) {
decompile(jadxArgs, null, null);
protected JadxDecompiler decompile(JadxArgs jadxArgs) {
return decompile(jadxArgs, null, null);
}
protected void decompile(JadxArgs jadxArgs, String clsPatternStr) {
decompile(jadxArgs, clsPatternStr, null);
protected JadxDecompiler decompile(JadxArgs jadxArgs, String clsPatternStr) {
return decompile(jadxArgs, clsPatternStr, null);
}
protected void decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.getPluginManager().unload("java-convert");
jadx.load();
......@@ -61,6 +61,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
processByPatterns(jadx, clsPatternStr, mthPatternStr);
}
printErrorReport(jadx);
return jadx;
}
private void processAll(JadxDecompiler jadx) {
......
package jadx.tests.integration.others;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.CheckClassAdapter;
import jadx.core.utils.files.FileUtils;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestJavaSwap extends IntegrationTest {
@SuppressWarnings("StringBufferReplaceableByString")
public static class TestCls {
private Iterable<String> field;
@Override
public String toString() {
String string = String.valueOf(this.field);
return new StringBuilder(8 + String.valueOf(string).length())
.append("concat(").append(string).append(")")
.toString();
}
}
@Test
public void testJava() {
useJavaInput();
assertThat(getClassNode(TestCls.class))
.code();
}
@Test
public void test() throws IOException {
// TODO: find up-to-date assembler/disassembler in java
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, 0, "TestCls", null, "java/lang/Object", new String[] {});
cw.visitField(Opcodes.ACC_PRIVATE, "field", "Ljava/lang/Iterable;", null, null).visitEnd();
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, new String[] {});
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "TestCls", "field", "Ljava/lang/Iterable;");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitVarInsn(Opcodes.ASTORE, 1);
mv.visitIntInsn(Opcodes.BIPUSH, 8);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false);
mv.visitInsn(Opcodes.IADD);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP_X1);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(I)V", false);
mv.visitLdcInsn("concat(");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(")");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0); // auto calculated
mv.visitEnd();
cw.visitEnd();
byte[] clsBytes = cw.toByteArray();
StringWriter results = new StringWriter();
CheckClassAdapter.verify(new ClassReader(clsBytes), false, new PrintWriter(results));
assertThat(results.toString()).isEmpty();
Path clsFile = FileUtils.createTempFile(".class");
Files.write(clsFile, clsBytes);
List<File> files = Collections.singletonList(clsFile.toFile());
useJavaInput();
assertThat(getClassNodeFromFiles(files, "TestCls"))
.code();
}
}
......@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.ISeqConsumer;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
......@@ -92,7 +93,7 @@ public class DexClassData implements IClassData {
}
@Override
public void visitFieldsAndMethods(Consumer<IFieldData> fieldConsumer, Consumer<IMethodData> mthConsumer) {
public void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldConsumer, ISeqConsumer<IMethodData> mthConsumer) {
int classDataOff = getClassDataOff();
if (classDataOff == 0) {
return;
......@@ -103,6 +104,9 @@ public class DexClassData implements IClassData {
int directMthCount = data.readUleb128();
int virtualMthCount = data.readUleb128();
fieldConsumer.init(staticFieldsCount + instanceFieldsCount);
mthConsumer.init(directMthCount + virtualMthCount);
annotationsParser.setOffset(getAnnotationsOff());
visitFields(fieldConsumer, data, staticFieldsCount, instanceFieldsCount);
visitMethods(mthConsumer, data, directMthCount, virtualMthCount);
......
package jadx.plugins.input.javaconvert;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
......@@ -20,6 +17,7 @@ import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.plugins.utils.ZipSecurity;
public class JavaConvertLoader {
......@@ -81,7 +79,7 @@ public class JavaConvertLoader {
try {
String entryName = entry.getName();
if (entryName.endsWith(".jar")) {
Path tempJar = saveInputStreamToFile(in, ".jar");
Path tempJar = CommonFileUtils.saveToTempFile(in, ".jar");
result.addTempPath(tempJar);
LOG.debug("Loading jar: {} ...", entryName);
convertJar(result, tempJar);
......@@ -134,14 +132,14 @@ public class JavaConvertLoader {
|| entryName.startsWith("META-INF/versions/")) {
return;
}
byte[] clsFileContent = inputStreamToByteArray(in);
byte[] clsFileContent = CommonFileUtils.loadBytes(in);
String clsName = AsmUtils.getNameFromClassFile(clsFileContent);
if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) {
throw new IOException("Can't read class name from file: " + entryName);
}
addJarEntry(jo, clsName + ".class", clsFileContent, entry.getLastModifiedTime());
} else if (entryName.endsWith(".jar")) {
Path tempJar = saveInputStreamToFile(in, ".jar");
Path tempJar = CommonFileUtils.saveToTempFile(in, ".jar");
result.addTempPath(tempJar);
convertJar(result, tempJar);
}
......@@ -190,32 +188,4 @@ public class JavaConvertLoader {
jar.write(content);
jar.closeEntry();
}
private static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[8 * 1024];
while (true) {
int count = input.read(buffer);
if (count == -1) {
break;
}
output.write(buffer, 0, count);
}
}
private static byte[] inputStreamToByteArray(InputStream input) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
copyStream(input, output);
return output.toByteArray();
}
}
private static Path saveInputStreamToFile(InputStream in, String suffix) throws IOException {
Path tempJar = Files.createTempFile("jadx-temp-", suffix);
try (OutputStream out = Files.newOutputStream(tempJar)) {
copyStream(in, out);
} catch (Exception e) {
throw new IOException("Failed to save temp file", e);
}
return tempJar;
}
}
package jadx.plugins.input.java;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
......@@ -13,9 +12,11 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.api.plugins.utils.ZipSecurity;
public class JavaFileLoader {
......@@ -38,35 +39,53 @@ public class JavaFileLoader {
private List<JavaClassReader> loadFromFile(File file) {
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return loadReader(file, inputStream, file.getAbsolutePath());
return loadReader(inputStream, file.getName(), file, null);
} catch (Exception e) {
LOG.error("File open error: {}", file.getAbsolutePath(), e);
return Collections.emptyList();
}
}
private List<JavaClassReader> loadReader(File file, InputStream in, String inputFileName) throws IOException {
private List<JavaClassReader> loadReader(InputStream in, String name,
@Nullable File file, @Nullable String parentFileName) throws IOException {
byte[] magic = new byte[MAX_MAGIC_SIZE];
if (in.read(magic) != magic.length) {
return Collections.emptyList();
}
if (isStartWithBytes(magic, JAVA_CLASS_FILE_MAGIC)) {
byte[] data = loadBytes(magic, in);
JavaClassReader reader = new JavaClassReader(getNextUniqId(), inputFileName, data);
byte[] data = CommonFileUtils.loadBytes(magic, in);
String source = concatSource(parentFileName, name);
JavaClassReader reader = new JavaClassReader(getNextUniqId(), source, data);
return Collections.singletonList(reader);
}
if (file != null && isStartWithBytes(magic, ZIP_FILE_MAGIC)) {
return collectFromZip(file);
if (isStartWithBytes(magic, ZIP_FILE_MAGIC)) {
if (file != null) {
return collectFromZip(file, name);
}
File zipFile = CommonFileUtils.saveToTempFile(magic, in, ".zip").toFile();
return collectFromZip(zipFile, concatSource(parentFileName, name));
}
return Collections.emptyList();
}
private List<JavaClassReader> collectFromZip(File file) {
private static String concatSource(@Nullable String parentFileName, String name) {
if (parentFileName == null) {
return name;
}
return parentFileName + ':' + name;
}
private List<JavaClassReader> collectFromZip(File file, String name) {
List<JavaClassReader> result = new ArrayList<>();
try {
ZipSecurity.readZipEntries(file, (entry, in) -> {
try {
result.addAll(loadReader(null, in, entry.getName()));
String entryName = entry.getName();
if (entryName.startsWith("META-INF/versions/")) {
// skip classes for different java versions
return;
}
result.addAll(loadReader(in, entryName, null, name));
} catch (Exception e) {
LOG.error("Failed to read zip entry: {}", entry, e);
}
......@@ -90,21 +109,6 @@ public class JavaFileLoader {
return true;
}
public static byte[] loadBytes(byte[] prefix, InputStream in) throws IOException {
int estimateSize = prefix.length + in.available();
ByteArrayOutputStream out = new ByteArrayOutputStream(estimateSize);
out.write(prefix);
byte[] buffer = new byte[8 * 1024];
while (true) {
int len = in.read(buffer);
if (len == -1) {
break;
}
out.write(buffer, 0, len);
}
return out.toByteArray();
}
private int getNextUniqId() {
return classUniqId++;
}
......
......@@ -239,14 +239,17 @@ public class ConstPoolReader {
private String fixType(String clsName) {
switch (clsName.charAt(0)) {
case 'L':
case 'T':
case '[':
return clsName;
default:
return 'L' + clsName + ';';
case 'L':
case 'T':
if (clsName.endsWith(";")) {
return clsName;
}
break;
}
return 'L' + clsName + ';';
}
private void jumpToData(int idx) {
......
......@@ -3,7 +3,6 @@ package jadx.plugins.input.java.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
......@@ -11,6 +10,7 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.ISeqConsumer;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.api.plugins.utils.Utils;
import jadx.plugins.input.java.JavaClassReader;
......@@ -74,11 +74,12 @@ public class JavaClassData implements IClassData {
}
@Override
public void visitFieldsAndMethods(Consumer<IFieldData> fieldsConsumer, Consumer<IMethodData> mthConsumer) {
public void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldsConsumer, ISeqConsumer<IMethodData> mthConsumer) {
int clsIdx = data.absPos(offsets.getClsTypeOffset()).readU2();
String classType = constPoolReader.getClass(clsIdx);
DataReader reader = data.absPos(offsets.getFieldsOffset()).copy();
int fieldsCount = reader.readU2();
fieldsConsumer.init(fieldsCount);
if (fieldsCount != 0) {
JavaFieldData field = new JavaFieldData();
field.setParentClassType(classType);
......@@ -89,6 +90,7 @@ public class JavaClassData implements IClassData {
}
int methodsCount = reader.readU2();
mthConsumer.init(methodsCount);
if (methodsCount != 0) {
JavaMethodRef methodRef = new JavaMethodRef();
methodRef.setParentClassType(classType);
......
......@@ -16,7 +16,7 @@ public class AttributesReader {
private final JavaClassData clsData;
private final ConstPoolReader constPool;
private final Map<Integer, JavaAttrType<?>> attrMap = new HashMap<>(JavaAttrType.size());
private final Map<Integer, JavaAttrType<?>> attrCache = new HashMap<>(JavaAttrType.size());
public AttributesReader(JavaClassData clsData, ConstPoolReader constPoolReader) {
this.clsData = clsData;
......@@ -95,8 +95,8 @@ public class AttributesReader {
}
private JavaAttrType<?> resolveAttrReader(int nameIdx) {
return attrMap.computeIfAbsent(nameIdx, idx -> {
String attrName = constPool.getUtf8(nameIdx);
return attrCache.computeIfAbsent(nameIdx, idx -> {
String attrName = constPool.getUtf8(idx);
JavaAttrType<?> attrType = JavaAttrType.byName(attrName);
if (attrType == null) {
LOG.warn("Unknown java class attribute type: {}", attrName);
......
......@@ -39,6 +39,8 @@ public final class JavaAttrType<T extends IJavaAttribute> {
public static final JavaAttrType<JavaAnnotationsAttr> BUILD_ANNOTATIONS;
public static final JavaAttrType<JavaParamAnnsAttr> RUNTIME_PARAMETER_ANNOTATIONS;
public static final JavaAttrType<JavaParamAnnsAttr> BUILD_PARAMETER_ANNOTATIONS;
public static final JavaAttrType<IgnoredAttr> RUNTIME_TYPE_ANNOTATIONS;
public static final JavaAttrType<IgnoredAttr> BUILD_TYPE_ANNOTATIONS;
public static final JavaAttrType<JavaAnnotationDefaultAttr> ANNOTATION_DEFAULT;
public static final JavaAttrType<JavaSourceFileAttr> SOURCE_FILE;
......@@ -46,7 +48,10 @@ public final class JavaAttrType<T extends IJavaAttribute> {
public static final JavaAttrType<JavaExceptionsAttr> EXCEPTIONS;
public static final JavaAttrType<IgnoredAttr> DEPRECATED;
public static final JavaAttrType<IgnoredAttr> SYNTHETIC;
public static final JavaAttrType<IgnoredAttr> STACK_MAP_TABLE;
public static final JavaAttrType<IgnoredAttr> ENCLOSING_METHOD;
public static final JavaAttrType<IgnoredAttr> MODULE;
static {
NAME_TO_TYPE_MAP = new HashMap<>();
......@@ -74,7 +79,14 @@ public final class JavaAttrType<T extends IJavaAttribute> {
// ignored
DEPRECATED = bind("Deprecated", null); // duplicated by annotation
SYNTHETIC = bind("Synthetic", null); // duplicated by access flag
STACK_MAP_TABLE = bind("StackMapTable", null);
ENCLOSING_METHOD = bind("EnclosingMethod", null);
// TODO: not supported yet
RUNTIME_TYPE_ANNOTATIONS = bind("RuntimeVisibleTypeAnnotations", null);
BUILD_TYPE_ANNOTATIONS = bind("RuntimeInvisibleTypeAnnotations", null);
MODULE = bind("Module", null);
}
private static <A extends IJavaAttribute> JavaAttrType<A> bind(String name, IJavaAttributeReader reader) {
......@@ -113,4 +125,9 @@ public final class JavaAttrType<T extends IJavaAttribute> {
public IJavaAttributeReader getReader() {
return reader;
}
@Override
public String toString() {
return name;
}
}
......@@ -52,8 +52,7 @@ public class JavaCodeReader implements ICodeReader {
@Override
public void visitInstructions(Consumer<InsnData> insnConsumer) {
Set<Integer> excHandlers = getExcHandlers();
reader.absPos(codeOffset);
int maxStack = reader.readU2();
int maxStack = readMaxStack();
reader.skip(2);
int codeSize = reader.readU4();
......@@ -96,16 +95,20 @@ public class JavaCodeReader implements ICodeReader {
@Override
public int getRegistersCount() {
reader.absPos(codeOffset);
int maxStack = reader.readU2();
int maxStack = readMaxStack();
int maxLocals = reader.readU2();
return maxStack + maxLocals;
}
@Override
public int getArgsStartReg() {
return readMaxStack();
}
private int readMaxStack() {
reader.absPos(codeOffset);
return reader.readU2(); // maxStack
int maxStack = reader.readU2();
return maxStack + 1; // add one temporary register (for `swap` opcode)
}
@Override
......@@ -114,9 +117,9 @@ public class JavaCodeReader implements ICodeReader {
}
@Override
public @Nullable IDebugInfo getDebugInfo() {
reader.absPos(codeOffset);
int maxStack = reader.readU2();
@Nullable
public IDebugInfo getDebugInfo() {
int maxStack = readMaxStack();
reader.skip(2);
reader.skip(reader.readU4());
reader.skip(reader.readU2() * 8);
......
......@@ -10,6 +10,7 @@ import jadx.plugins.input.java.data.code.decoders.InvokeDecoder;
import jadx.plugins.input.java.data.code.decoders.LoadConstDecoder;
import jadx.plugins.input.java.data.code.decoders.LookupSwitchDecoder;
import jadx.plugins.input.java.data.code.decoders.TableSwitchDecoder;
import jadx.plugins.input.java.data.code.decoders.WideDecoder;
import static jadx.plugins.input.java.data.code.StackState.SVType.NARROW;
import static jadx.plugins.input.java.data.code.StackState.SVType.WIDE;
......@@ -172,6 +173,10 @@ public class JavaInsnsRegister {
.peekFrom(2, 4).peekFrom(0, 5);
}
});
register(arr, 0x5f, "swap", 0, 6, Opcode.MOVE_MULTI,
s -> s.peekFrom(-1, 0).peekFrom(1, 1)
.peekFrom(1, 2).peekFrom(0, 3)
.peekFrom(0, 4).peekFrom(-1, 5));
register(arr, 0x60, "iadd", 0, 3, Opcode.ADD_INT, twoRegsWithResult(NARROW));
register(arr, 0x61, "ladd", 0, 3, Opcode.ADD_LONG, twoRegsWithResult(WIDE));
......@@ -295,7 +300,7 @@ public class JavaInsnsRegister {
register(arr, 0xc2, "monitorenter", 0, 1, Opcode.MONITOR_ENTER, s -> s.pop(0));
register(arr, 0xc3, "monitorexit", 0, 1, Opcode.MONITOR_EXIT, s -> s.pop(0));
// register(arr, 0xc4, "wide", 0, 1, Opcode.NOP, s -> s.pop(0));
register(arr, 0xc4, "wide", -1, -1, Opcode.NOP, new WideDecoder());
register(arr, 0xc5, "multianewarray", 3, -1, Opcode.NEW_ARRAY, InsnIndexType.TYPE_REF, newArrayMulti());
register(arr, 0xc6, "ifnull", 2, 1, Opcode.IF_EQZ, zeroCmp());
......
package jadx.plugins.input.java.data.code.decoders;
import jadx.api.plugins.input.insns.Opcode;
import jadx.plugins.input.java.data.DataReader;
import jadx.plugins.input.java.data.code.CodeDecodeState;
import jadx.plugins.input.java.data.code.JavaInsnData;
import jadx.plugins.input.java.utils.JavaClassParseException;
public class WideDecoder implements IJavaInsnDecoder {
private static final int IINC = 0x84;
@Override
public void decode(CodeDecodeState state) {
DataReader reader = state.reader();
JavaInsnData insn = state.insn();
int opcode = reader.readU1();
if (opcode == IINC) {
int varNum = reader.readU2();
int constValue = reader.readS2();
state.local(0, varNum).local(1, varNum).lit(constValue);
insn.setPayloadSize(5);
insn.setRegsCount(2);
insn.setOpcode(Opcode.ADD_INT_LIT);
return;
}
int index = reader.readU2();
switch (opcode) {
case 0x15: // iload,
case 0x17: // fload
case 0x19: // aload
state.local(1, index).push(0);
break;
case 0x16: // lload
case 0x18: // dload
state.local(1, index).pushWide(0);
break;
case 0x36:
case 0x37:
case 0x38:
case 0x39:
case 0x3a:
// *store
state.pop(1).local(0, index);
break;
default:
throw new JavaClassParseException("Unexpected opcode in 'wide': 0x" + Integer.toHexString(opcode));
}
insn.setPayloadSize(3);
insn.setRegsCount(2);
insn.setOpcode(Opcode.MOVE);
}
@Override
public void skip(CodeDecodeState state) {
DataReader reader = state.reader();
JavaInsnData insn = state.insn();
int opcode = reader.readU1();
if (opcode == IINC) {
reader.skip(4);
insn.setPayloadSize(5);
} else {
reader.skip(2);
insn.setPayloadSize(3);
}
}
}
package jadx.api.plugins.input.data;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
......@@ -21,7 +20,7 @@ public interface IClassData {
List<String> getInterfacesTypes();
void visitFieldsAndMethods(Consumer<IFieldData> fieldsConsumer, Consumer<IMethodData> mthConsumer);
void visitFieldsAndMethods(ISeqConsumer<IFieldData> fieldsConsumer, ISeqConsumer<IMethodData> mthConsumer);
List<IJadxAttribute> getAttributes();
......
package jadx.api.plugins.input.data;
import java.util.function.Consumer;
/**
* "Sequence consumer" allows getting count of elements available
*/
public interface ISeqConsumer<T> extends Consumer<T> {
default void init(int count) {
// no-op implementation
}
}
package jadx.api.plugins.input.data.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import jadx.api.plugins.input.data.ISeqConsumer;
public class ListConsumer<T, R> implements ISeqConsumer<T> {
private final Function<T, R> convert;
private List<R> list;
public ListConsumer(Function<T, R> convert) {
this.convert = convert;
}
@Override
public void init(int count) {
list = count == 0 ? Collections.emptyList() : new ArrayList<>(count);
}
@Override
public void accept(T t) {
list.add(convert.apply(t));
}
public List<R> getResult() {
if (list == null) {
// init not called
return Collections.emptyList();
}
return list;
}
}
package jadx.api.plugins.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class CommonFileUtils {
public static Path saveToTempFile(InputStream in, String suffix) throws IOException {
return saveToTempFile(null, in, suffix);
}
public static Path saveToTempFile(byte[] dataPrefix, InputStream in, String suffix) throws IOException {
Path tempFile = Files.createTempFile("jadx-temp-", suffix);
try (OutputStream out = Files.newOutputStream(tempFile)) {
if (dataPrefix != null) {
out.write(dataPrefix);
}
copyStream(in, out);
} catch (Exception e) {
throw new IOException("Failed to save temp file", e);
}
return tempFile;
}
public static byte[] loadBytes(InputStream input) throws IOException {
return loadBytes(null, input);
}
public static byte[] loadBytes(byte[] dataPrefix, InputStream in) throws IOException {
int estimateSize = dataPrefix == null ? in.available() : dataPrefix.length + in.available();
try (ByteArrayOutputStream out = new ByteArrayOutputStream(estimateSize)) {
if (dataPrefix != null) {
out.write(dataPrefix);
}
copyStream(in, out);
return out.toByteArray();
} catch (Exception e) {
throw new IOException("Failed to read input stream to bytes array", e);
}
}
public static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[8 * 1024];
while (true) {
int count = input.read(buffer);
if (count == -1) {
break;
}
output.write(buffer, 0, count);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册