feat(java-input): support StackMapTable to get stack info for unvisited jumps (#2271)

This commit is contained in:
Skylot
2024-09-14 22:33:28 +01:00
parent 7bb5c0a859
commit 5c83c22501
21 changed files with 620 additions and 131 deletions
@@ -71,7 +71,11 @@ public class CheckCode extends AbstractVisitor {
}
insnNode.getRegisterArgs(list);
for (RegisterArg arg : list) {
if (arg.getRegNum() >= regsCount) {
int regNum = arg.getRegNum();
if (regNum < 0) {
throw new JadxRuntimeException("Incorrect negative register number in instruction: " + insnNode);
}
if (regNum >= regsCount) {
throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode
+ ", expected to be less than " + regsCount);
}
@@ -90,7 +90,7 @@ public class BlockProcessor extends AbstractVisitor {
}
}
private static boolean deduplicateBlockInsns(BlockNode block) {
private static boolean deduplicateBlockInsns(MethodNode mth, BlockNode block) {
if (block.contains(AFlag.LOOP_START) || block.contains(AFlag.LOOP_END)) {
// search for same instruction at end of all predecessors blocks
List<BlockNode> predecessors = block.getPredecessors();
@@ -109,7 +109,7 @@ public class BlockProcessor extends AbstractVisitor {
List<InsnNode> insns = getLastInsns(predecessors.get(0), sameInsnCount);
insertAtStart(block, insns);
predecessors.forEach(pred -> getLastInsns(pred, sameInsnCount).clear());
LOG.debug("Move duplicate insns, count: {} to block {}", sameInsnCount, block);
mth.addDebugComment("Move duplicate insns, count: " + sameInsnCount + " to block " + block);
return true;
}
}
@@ -318,7 +318,7 @@ public class BlockProcessor extends AbstractVisitor {
boolean changed = false;
List<BlockNode> basicBlocks = mth.getBasicBlocks();
for (BlockNode basicBlock : basicBlocks) {
if (deduplicateBlockInsns(basicBlock)) {
if (deduplicateBlockInsns(mth, basicBlock)) {
changed = true;
}
}
@@ -0,0 +1,40 @@
package jadx.tests.integration.jbc;
import org.junit.jupiter.api.Test;
import jadx.tests.api.RaungTest;
import jadx.tests.api.extensions.profiles.TestProfile;
import jadx.tests.api.extensions.profiles.TestWithProfiles;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestStackConvert extends RaungTest {
@SuppressWarnings({ "UnnecessaryLocalVariable", "CallToPrintStackTrace", "printstacktrace" })
public static class TestCls {
public int parseIntDefault(String num, int defaultNum) {
try {
int defaultNum2 = Integer.parseInt(num);
return defaultNum2;
} catch (NumberFormatException e) {
System.out.println("Before println");
e.printStackTrace();
return defaultNum;
}
}
}
@TestWithProfiles(TestProfile.JAVA11)
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("Integer.parseInt(num)");
}
@Test
public void testRaung() {
assertThat(getClassNodeFromRaung())
.code()
.containsOne("Integer.parseInt(num)");
}
}
@@ -0,0 +1,53 @@
.version 52 # Java 8
.class public super jbc/TestStackConvert
.method public parseIntDefault(Ljava/lang/String;I)I
.max stack 3
.max locals 5
:L0
.local 0 "this" Ljbc/TestStackConvert;
.local 1 "num" Ljava/lang/String;
.local 2 "defaultNum" I
.line 13
aload 1
invokestatic java/lang/Integer parseInt (Ljava/lang/String;)I
:L1
.catch java/lang/NumberFormatException :L0 .. :L1 goto :L2
ireturn
:L3
.line 14
.stack full
stack 0 java/lang/NumberFormatException
local 0 jbc/TestStackConvert
local 1 java/lang/String
local 2 int
local 3 Top
local 4 java/lang/NumberFormatException
.end stack
astore 3
.local 3 "e" Ljava/lang/NumberFormatException;
.line 15
aload 3
invokevirtual java/lang/NumberFormatException printStackTrace ()V
.line 17
iload 2
.end local 3 # "e"
ireturn
:L2
.stack full
stack 0 java/lang/NumberFormatException
local 0 jbc/TestStackConvert
local 1 java/lang/String
local 2 int
.end stack
.end local 0 # "this"
.end local 1 # "num"
.end local 2 # "defaultNum"
astore 4
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Before println"
invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
aload 4
goto :L3
.end method
@@ -103,7 +103,7 @@ public class ConstPoolReader {
}
private CallSite resolveMethodCallSite(int bootstrapMthIdx, int nameIdx, int descIdx) {
JavaBootstrapMethodsAttr bootstrapMethodsAttr = clsData.loadAttribute(data, JavaAttrType.BOOTSTRAP_METHODS);
JavaBootstrapMethodsAttr bootstrapMethodsAttr = clsData.loadClassAttribute(data, JavaAttrType.BOOTSTRAP_METHODS);
if (bootstrapMethodsAttr == null) {
throw new JavaClassParseException("Unexpected missing BootstrapMethods attribute");
}
@@ -106,7 +106,7 @@ public class JavaClassData implements IClassData {
int accessFlags = reader.readU2();
int nameIdx = reader.readU2();
int typeIdx = reader.readU2();
JavaAttrStorage attributes = attributesReader.load(reader);
JavaAttrStorage attributes = attributesReader.loadAll(reader);
field.setAccessFlags(accessFlags);
field.setName(constPoolReader.getUtf8(nameIdx));
@@ -118,7 +118,7 @@ public class JavaClassData implements IClassData {
int accessFlags = reader.readU2();
int nameIdx = reader.readU2();
int descriptorIdx = reader.readU2();
JavaAttrStorage attributes = attributesReader.load(reader);
JavaAttrStorage attributes = attributesReader.loadAll(reader);
JavaMethodRef methodRef = method.getMethodRef();
methodRef.reset();
@@ -140,7 +140,7 @@ public class JavaClassData implements IClassData {
@Override
public List<IJadxAttribute> getAttributes() {
data.absPos(offsets.getAttributesOffset());
JavaAttrStorage attributes = attributesReader.load(data);
JavaAttrStorage attributes = attributesReader.loadAll(data);
int size = attributes.size();
if (size == 0) {
return Collections.emptyList();
@@ -153,9 +153,9 @@ public class JavaClassData implements IClassData {
return list;
}
public <T extends IJavaAttribute> T loadAttribute(DataReader reader, JavaAttrType<T> type) {
public <T extends IJavaAttribute> T loadClassAttribute(DataReader reader, JavaAttrType<T> type) {
reader.absPos(offsets.getAttributesOffset());
return attributesReader.loadOne(type, reader);
return attributesReader.loadOne(reader, type);
}
@Override
@@ -2,6 +2,8 @@ package jadx.plugins.input.java.data.attributes;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -14,6 +16,8 @@ import jadx.plugins.input.java.data.JavaClassData;
public class AttributesReader {
private static final Logger LOG = LoggerFactory.getLogger(AttributesReader.class);
private static final Predicate<JavaAttrType<?>> LOAD_ALL = type -> true;
private final JavaClassData clsData;
private final ConstPoolReader constPool;
private final Map<Integer, JavaAttrType<?>> attrCache = new HashMap<>(JavaAttrType.size());
@@ -23,77 +27,71 @@ public class AttributesReader {
this.constPool = constPoolReader;
}
public JavaAttrStorage load(DataReader reader) {
int attributesCount = reader.readU2();
if (attributesCount == 0) {
public JavaAttrStorage loadAll(DataReader reader) {
return loadAttributes(reader, LOAD_ALL);
}
public JavaAttrStorage loadMulti(DataReader reader, Set<JavaAttrType<?>> types) {
return loadAttributes(reader, types::contains);
}
/**
* Load attributes into storage
*
* @param reader - reader pos should be set to attributes section start
* @param condition - check if attribute should be parsed and added to storage
*/
private JavaAttrStorage loadAttributes(DataReader reader, Predicate<JavaAttrType<?>> condition) {
int count = reader.readU2();
if (count == 0) {
return JavaAttrStorage.EMPTY;
}
JavaAttrStorage storage = new JavaAttrStorage();
for (int i = 0; i < attributesCount; i++) {
readAndAdd(storage, reader);
for (int i = 0; i < count; i++) {
int nameIdx = reader.readU2();
int len = reader.readU4();
int end = reader.getOffset() + len;
try {
JavaAttrType<?> attrType = resolveAttrReader(nameIdx);
if (attrType != null && condition.test(attrType)) {
IJavaAttributeReader attrReader = attrType.getReader();
if (attrReader != null) {
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);
}
}
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;
public <T extends IJavaAttribute> @Nullable T loadOne(DataReader reader, JavaAttrType<T> type) {
int count = reader.readU2();
for (int i = 0; i < count; i++) {
int nameIdx = reader.readU2();
int len = reader.readU4();
int end = reader.getOffset() + len;
try {
JavaAttrType<?> attrType = resolveAttrReader(nameIdx);
if (attrType == type) {
return (T) attrType.getReader().read(clsData, reader);
}
} catch (Exception e) {
LOG.error("Failed to parse attribute: {}", constPool.getUtf8(nameIdx), e);
} finally {
reader.absPos(end);
}
}
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 attrCache.computeIfAbsent(nameIdx, idx -> {
String attrName = constPool.getUtf8(idx);
@@ -9,6 +9,7 @@ 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.stack.StackMapTableReader;
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;
@@ -21,6 +22,7 @@ import jadx.plugins.input.java.data.attributes.types.JavaMethodParametersAttr;
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;
import jadx.plugins.input.java.data.attributes.types.StackMapTableAttr;
public final class JavaAttrType<T extends IJavaAttribute> {
@@ -32,6 +34,7 @@ public final class JavaAttrType<T extends IJavaAttribute> {
public static final JavaAttrType<ConstValueAttr> CONST_VALUE;
public static final JavaAttrType<CodeAttr> CODE;
public static final JavaAttrType<StackMapTableAttr> STACK_MAP_TABLE;
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;
@@ -51,9 +54,12 @@ public final class JavaAttrType<T extends IJavaAttribute> {
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;
public static final JavaAttrType<IgnoredAttr> SOURCE_DEBUG_EXTENSION;
public static final JavaAttrType<IgnoredAttr> NEST_HOST;
public static final JavaAttrType<IgnoredAttr> NEST_MEMBERS;
static {
NAME_TO_TYPE_MAP = new HashMap<>();
@@ -79,17 +85,20 @@ public final class JavaAttrType<T extends IJavaAttribute> {
SIGNATURE = bind("Signature", JavaSignatureAttr.reader());
EXCEPTIONS = bind("Exceptions", JavaExceptionsAttr.reader());
METHOD_PARAMETERS = bind("MethodParameters", JavaMethodParametersAttr.reader());
STACK_MAP_TABLE = bind("StackMapTable", new StackMapTableReader());
// 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);
NEST_HOST = bind("NestHost", null);
NEST_MEMBERS = bind("NestMembers", null);
SOURCE_DEBUG_EXTENSION = bind("SourceDebugExtension", null);
}
private static <A extends IJavaAttribute> JavaAttrType<A> bind(String name, IJavaAttributeReader reader) {
@@ -129,6 +138,19 @@ public final class JavaAttrType<T extends IJavaAttribute> {
return reader;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
return id == ((JavaAttrType<?>) o).id;
}
@Override
public String toString() {
return name;
@@ -0,0 +1,47 @@
package jadx.plugins.input.java.data.attributes.stack;
public class StackFrame {
private final int offset;
private final StackFrameType type;
private int stackSize;
private StackValueType[] stackValueTypes;
private int localsCount;
public StackFrame(int offset, StackFrameType type) {
this.offset = offset;
this.type = type;
}
public int getOffset() {
return offset;
}
public StackFrameType getType() {
return type;
}
public int getLocalsCount() {
return localsCount;
}
public void setLocalsCount(int localsCount) {
this.localsCount = localsCount;
}
public int getStackSize() {
return stackSize;
}
public void setStackSize(int stackSize) {
this.stackSize = stackSize;
}
public StackValueType[] getStackValueTypes() {
return stackValueTypes;
}
public void setStackValueTypes(StackValueType[] stackValueTypes) {
this.stackValueTypes = stackValueTypes;
}
}
@@ -0,0 +1,37 @@
package jadx.plugins.input.java.data.attributes.stack;
import org.jetbrains.annotations.Nullable;
public enum StackFrameType {
SAME_FRAME(0, 63),
SAME_LOCALS_1_STACK(64, 127),
SAME_LOCALS_1_STACK_EXTENDED(247, 247),
CHOP(248, 250),
SAME_FRAME_EXTENDED(251, 251),
APPEND(252, 254),
FULL(255, 255);
private final int start;
private final int end;
StackFrameType(int start, int end) {
this.start = start;
this.end = end;
}
private static final StackFrameType[] MAPPING = buildMapping();
private static StackFrameType[] buildMapping() {
StackFrameType[] mapping = new StackFrameType[256];
for (StackFrameType value : StackFrameType.values()) {
for (int i = value.start; i <= value.end; i++) {
mapping[i] = value;
}
}
return mapping;
}
public static @Nullable StackFrameType getType(int data) {
return MAPPING[data];
}
}
@@ -0,0 +1,176 @@
package jadx.plugins.input.java.data.attributes.stack;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import jadx.plugins.input.java.data.DataReader;
import jadx.plugins.input.java.data.JavaClassData;
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
import jadx.plugins.input.java.data.attributes.IJavaAttributeReader;
import jadx.plugins.input.java.data.attributes.types.StackMapTableAttr;
import jadx.plugins.input.java.utils.JavaClassParseException;
public class StackMapTableReader implements IJavaAttributeReader {
@Override
public IJavaAttribute read(JavaClassData clsData, DataReader reader) {
int count = reader.readU2();
Map<Integer, StackFrame> map = new HashMap<>(count, 1);
StackFrame prevFrame = null;
for (int i = 0; i < count; i++) {
StackFrame frame = readFrame(reader, prevFrame);
map.put(frame.getOffset(), frame);
prevFrame = frame;
}
return new StackMapTableAttr(map);
}
private static final Map<StackFrameType, Consumer<FrameContext>> FRAME_READERS = registerReaders();
private static Map<StackFrameType, Consumer<FrameContext>> registerReaders() {
EnumMap<StackFrameType, Consumer<FrameContext>> map = new EnumMap<>(StackFrameType.class);
map.put(StackFrameType.SAME_FRAME, context -> readSame(context, false));
map.put(StackFrameType.SAME_FRAME_EXTENDED, context -> readSame(context, true));
map.put(StackFrameType.SAME_LOCALS_1_STACK, context -> readSL1S(context, false));
map.put(StackFrameType.SAME_LOCALS_1_STACK_EXTENDED, context -> readSL1S(context, true));
map.put(StackFrameType.CHOP, StackMapTableReader::readChop);
map.put(StackFrameType.APPEND, StackMapTableReader::readAppend);
map.put(StackFrameType.FULL, StackMapTableReader::readFull);
return map;
}
private StackFrame readFrame(DataReader reader, StackFrame prevFrame) {
int typeData = reader.readU1();
StackFrameType frameType = StackFrameType.getType(typeData);
Consumer<FrameContext> frameReader = FRAME_READERS.get(frameType);
if (frameReader == null) {
throw new JavaClassParseException("Found unsupported stack frame type: " + frameType);
}
FrameContext frameContext = new FrameContext(reader, typeData, prevFrame);
frameReader.accept(frameContext);
return Objects.requireNonNull(frameContext.getFrame());
}
private static void readSame(FrameContext context, boolean extended) {
int offsetDelta;
StackFrameType type;
if (extended) {
type = StackFrameType.SAME_FRAME_EXTENDED;
offsetDelta = context.getDataReader().readU2();
} else {
type = StackFrameType.SAME_FRAME;
offsetDelta = context.getTypeData();
}
StackFrame frame = new StackFrame(calcOffset(context, offsetDelta), type);
frame.setStackSize(0);
frame.setLocalsCount(getPrevLocalsCount(context));
context.setFrame(frame);
}
private static void readSL1S(FrameContext context, boolean extended) {
DataReader reader = context.getDataReader();
int offsetDelta;
StackFrameType type;
if (extended) {
type = StackFrameType.SAME_LOCALS_1_STACK_EXTENDED;
offsetDelta = reader.readU2();
} else {
type = StackFrameType.SAME_LOCALS_1_STACK;
offsetDelta = context.getTypeData() - 64;
}
StackValueType[] stackTypes = TypeInfoReader.readTypeInfoList(reader, 1);
StackFrame frame = new StackFrame(calcOffset(context, offsetDelta), type);
frame.setStackSize(1);
frame.setStackValueTypes(stackTypes);
frame.setLocalsCount(getPrevLocalsCount(context));
context.setFrame(frame);
}
private static void readChop(FrameContext context) {
int k = 251 - context.getTypeData();
int offsetDelta = context.getDataReader().readU2();
StackFrame frame = new StackFrame(calcOffset(context, offsetDelta), StackFrameType.CHOP);
frame.setStackSize(0);
frame.setLocalsCount(getPrevLocalsCount(context) - k);
context.setFrame(frame);
}
private static void readAppend(FrameContext context) {
DataReader reader = context.getDataReader();
int k = context.getTypeData() - 251;
int offsetDelta = reader.readU2();
TypeInfoReader.skipTypeInfoList(reader, k);
StackFrame frame = new StackFrame(calcOffset(context, offsetDelta), StackFrameType.APPEND);
frame.setStackSize(0);
frame.setLocalsCount(getPrevLocalsCount(context) - k);
context.setFrame(frame);
}
private static void readFull(FrameContext context) {
DataReader reader = context.getDataReader();
int offsetDelta = reader.readU2();
int localsCount = reader.readU2();
TypeInfoReader.skipTypeInfoList(reader, localsCount);
int stackSize = reader.readU2();
StackValueType[] stackTypes = TypeInfoReader.readTypeInfoList(reader, stackSize);
StackFrame frame = new StackFrame(calcOffset(context, offsetDelta), StackFrameType.FULL);
frame.setLocalsCount(localsCount);
frame.setStackSize(stackSize);
frame.setStackValueTypes(stackTypes);
context.setFrame(frame);
}
private static int calcOffset(FrameContext context, int offsetDelta) {
StackFrame prevFrame = context.getPrevFrame();
if (prevFrame == null) {
return offsetDelta;
}
return prevFrame.getOffset() + offsetDelta + 1;
}
private static int getPrevLocalsCount(FrameContext context) {
StackFrame prevFrame = context.getPrevFrame();
if (prevFrame == null) {
return 0;
}
return prevFrame.getLocalsCount();
}
private static final class FrameContext {
private final DataReader dataReader;
private final int typeData;
private final StackFrame prevFrame;
private StackFrame frame;
private FrameContext(DataReader dataReader, int typeData, StackFrame prevFrame) {
this.dataReader = dataReader;
this.typeData = typeData;
this.prevFrame = prevFrame;
}
public DataReader getDataReader() {
return dataReader;
}
public int getTypeData() {
return typeData;
}
public StackFrame getPrevFrame() {
return prevFrame;
}
public StackFrame getFrame() {
return frame;
}
public void setFrame(StackFrame frame) {
this.frame = frame;
}
}
}
@@ -0,0 +1,6 @@
package jadx.plugins.input.java.data.attributes.stack;
public enum StackValueType {
NARROW, // int, float, etc
WIDE, // long, double
}
@@ -0,0 +1,53 @@
package jadx.plugins.input.java.data.attributes.stack;
import jadx.plugins.input.java.data.DataReader;
public class TypeInfoReader {
private static final int ITEM_TOP = 0;
private static final int ITEM_INT = 1;
private static final int ITEM_FLOAT = 2;
private static final int ITEM_DOUBLE = 3;
private static final int ITEM_LONG = 4;
private static final int ITEM_NULL = 5;
private static final int ITEM_UNINITIALIZED_THIS = 6;
private static final int ITEM_OBJECT = 7;
private static final int ITEM_UNINITIALIZED = 8;
static StackValueType[] readTypeInfoList(DataReader reader, int count) {
StackValueType[] types = new StackValueType[count];
for (int i = 0; i < count; i++) {
int tag = reader.readU1();
StackValueType type;
switch (tag) {
case ITEM_DOUBLE:
case ITEM_LONG:
type = StackValueType.WIDE;
break;
case ITEM_OBJECT:
case ITEM_UNINITIALIZED:
reader.readU2(); // ignore
type = StackValueType.NARROW;
break;
default:
type = StackValueType.NARROW;
break;
}
types[i] = type;
}
return types;
}
static void skipTypeInfoList(DataReader reader, int count) {
for (int i = 0; i < count; i++) {
int tag = reader.readU1();
if (tag == ITEM_OBJECT || tag == ITEM_UNINITIALIZED) {
reader.readU2();
}
}
}
}
@@ -0,0 +1,23 @@
package jadx.plugins.input.java.data.attributes.types;
import java.util.Collections;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.plugins.input.java.data.attributes.IJavaAttribute;
import jadx.plugins.input.java.data.attributes.stack.StackFrame;
public class StackMapTableAttr implements IJavaAttribute {
public static final StackMapTableAttr EMPTY = new StackMapTableAttr(Collections.emptyMap());
private final Map<Integer, StackFrame> map;
public StackMapTableAttr(Map<Integer, StackFrame> map) {
this.map = map;
}
public @Nullable StackFrame getFor(int offset) {
return map.get(offset);
}
}
@@ -4,10 +4,15 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.insns.Opcode;
import jadx.core.utils.Utils;
import jadx.plugins.input.java.data.DataReader;
import jadx.plugins.input.java.data.JavaClassData;
import jadx.plugins.input.java.data.code.StackState.SVType;
import jadx.plugins.input.java.data.attributes.stack.StackFrame;
import jadx.plugins.input.java.data.attributes.stack.StackValueType;
import jadx.plugins.input.java.data.attributes.types.StackMapTableAttr;
@SuppressWarnings("UnusedReturnValue")
public class CodeDecodeState {
@@ -15,35 +20,49 @@ public class CodeDecodeState {
private final DataReader reader;
private final int maxStack;
private final Set<Integer> excHandlers;
private final StackMapTableAttr stackMapTable;
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) {
public CodeDecodeState(JavaClassData clsData, DataReader reader, int maxStack,
Set<Integer> excHandlers, @Nullable StackMapTableAttr stackMapTable) {
this.clsData = clsData;
this.reader = reader;
this.maxStack = maxStack;
this.excHandlers = excHandlers;
this.stack = new StackState(maxStack);
this.stackMapTable = Utils.getOrElse(stackMapTable, StackMapTableAttr.EMPTY);
}
public void onInsn(int offset) {
StackState stackState = jumpStack.get(offset);
if (stackState != null) {
this.stack = stackState;
StackState newStack = loadStack(offset);
if (newStack != null) {
this.stack = newStack;
}
if (excHandlers.contains(offset)) {
clear();
stack.push(SVType.NARROW); // push exception
stack.push(StackValueType.NARROW); // push exception
excHandler = true;
} else {
excHandler = false;
}
}
private @Nullable StackState loadStack(int offset) {
StackState stackState = jumpStack.get(offset);
if (stackState != null) {
return stackState.copy();
}
StackFrame frame = stackMapTable.getFor(offset);
if (frame != null) {
return new StackState(maxStack).fillFromFrame(frame);
}
return null;
}
public void registerJump(int jumpOffset) {
Integer key = jumpOffset;
if (!jumpStack.containsKey(key)) {
@@ -90,7 +109,7 @@ public class CodeDecodeState {
return this;
}
public SVType peekType(int at) {
public StackValueType peekType(int at) {
return stack.peekTypeAt(at);
}
@@ -100,21 +119,21 @@ public class CodeDecodeState {
}
public CodeDecodeState push(int arg) {
insn.setArgReg(arg, stack.push(SVType.NARROW));
insn.setArgReg(arg, stack.push(StackValueType.NARROW));
return this;
}
public CodeDecodeState push(int arg, SVType type) {
public CodeDecodeState push(int arg, StackValueType type) {
insn.setArgReg(arg, stack.push(type));
return this;
}
public CodeDecodeState pushWide(int arg) {
insn.setArgReg(arg, stack.push(SVType.WIDE));
insn.setArgReg(arg, stack.push(StackValueType.WIDE));
return this;
}
public int insert(int pos, SVType type) {
public int insert(int pos, StackValueType type) {
return stack.insert(pos, type);
}
@@ -123,9 +142,9 @@ public class CodeDecodeState {
}
public void discardWord() {
SVType type = stack.peekTypeAt(0);
StackValueType type = stack.peekTypeAt(0);
stack.pop();
if (type == SVType.NARROW) {
if (type == StackValueType.NARROW) {
stack.pop();
}
}
@@ -162,16 +181,16 @@ public class CodeDecodeState {
return maxStack + local;
}
public SVType fieldType() {
public StackValueType fieldType() {
String type = insn.constPoolReader().getFieldType(insn().getIndex());
return getSVType(type);
}
public SVType getSVType(String type) {
public StackValueType getSVType(String type) {
if (type.equals("J") || type.equals("D")) {
return SVType.WIDE;
return StackValueType.WIDE;
}
return SVType.NARROW;
return StackValueType.NARROW;
}
public int u1() {
@@ -28,6 +28,7 @@ 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.attributes.types.StackMapTableAttr;
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;
@@ -52,11 +53,14 @@ public class JavaCodeReader implements ICodeReader {
@Override
public void visitInstructions(Consumer<InsnData> insnConsumer) {
Set<Integer> excHandlers = getExcHandlers();
jumpToCodeAttributes();
StackMapTableAttr stackMapTable = clsData.getAttributesReader().loadOne(reader, JavaAttrType.STACK_MAP_TABLE);
int maxStack = readMaxStack();
reader.skip(2);
int codeSize = reader.readU4();
CodeDecodeState state = new CodeDecodeState(clsData, reader, maxStack, excHandlers);
CodeDecodeState state = new CodeDecodeState(clsData, reader, maxStack, excHandlers, stackMapTable);
JavaInsnData insn = new JavaInsnData(state);
state.setInsn(insn);
int offset = 0;
@@ -116,15 +120,17 @@ public class JavaCodeReader implements ICodeReader {
return reader.absPos(codeOffset + 4).readU4();
}
private static final Set<JavaAttrType<?>> DEBUG_INFO_ATTRIBUTES = Set.of(
JavaAttrType.LINE_NUMBER_TABLE,
JavaAttrType.LOCAL_VAR_TABLE,
JavaAttrType.LOCAL_VAR_TYPE_TABLE);
@Override
@Nullable
public IDebugInfo getDebugInfo() {
int maxStack = readMaxStack();
reader.skip(2);
reader.skip(reader.readU4());
reader.skip(reader.readU2() * 8);
JavaAttrStorage attrs = clsData.getAttributesReader().load(reader);
jumpToCodeAttributes();
JavaAttrStorage attrs = clsData.getAttributesReader().loadMulti(reader, DEBUG_INFO_ATTRIBUTES);
LineNumberTableAttr linesAttr = attrs.get(JavaAttrType.LINE_NUMBER_TABLE);
LocalVarsAttr varsAttr = attrs.get(JavaAttrType.LOCAL_VAR_TABLE);
if (linesAttr == null && varsAttr == null) {
@@ -162,7 +168,7 @@ public class JavaCodeReader implements ICodeReader {
@Override
public List<ITry> getTries() {
skipToTries();
jumpToTries();
int excTableLen = reader.readU2();
if (excTableLen == 0) {
return Collections.emptyList();
@@ -212,7 +218,7 @@ public class JavaCodeReader implements ICodeReader {
}
private Set<Integer> getExcHandlers() {
skipToTries();
jumpToTries();
int excTableLen = reader.readU2();
if (excTableLen == 0) {
return Collections.emptySet();
@@ -227,9 +233,13 @@ public class JavaCodeReader implements ICodeReader {
return set;
}
private void skipToTries() {
private void jumpToTries() {
reader.absPos(codeOffset + 4);
int codeSize = reader.readU4();
reader.skip(codeSize);
reader.skip(reader.readU4()); // code length
}
private void jumpToCodeAttributes() {
jumpToTries();
reader.skip(reader.readU2() * 8); // exceptions table
}
}
@@ -4,7 +4,7 @@ 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.attributes.stack.StackValueType;
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;
@@ -12,8 +12,8 @@ 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;
import static jadx.plugins.input.java.data.attributes.stack.StackValueType.NARROW;
import static jadx.plugins.input.java.data.attributes.stack.StackValueType.WIDE;
@SuppressWarnings("SpellCheckingInspection")
public class JavaInsnsRegister {
@@ -351,11 +351,11 @@ public class JavaInsnsRegister {
};
}
private static IJavaInsnDecoder oneRegWithResult(SVType type) {
private static IJavaInsnDecoder oneRegWithResult(StackValueType type) {
return s -> s.pop(1).push(0, type);
}
private static IJavaInsnDecoder twoRegsWithResult(SVType type) {
private static IJavaInsnDecoder twoRegsWithResult(StackValueType type) {
return s -> s.pop(2).pop(1).push(0, type);
}
@@ -387,7 +387,7 @@ public class JavaInsnsRegister {
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);
state.push(0, apiOpcode == Opcode.CONST_WIDE ? StackValueType.WIDE : NARROW);
});
}
@@ -2,24 +2,18 @@ package jadx.plugins.input.java.data.code;
import java.util.Arrays;
import jadx.plugins.input.java.data.attributes.stack.StackFrame;
import jadx.plugins.input.java.data.attributes.stack.StackValueType;
public class StackState {
/**
* Stack value type
*/
public enum SVType {
NARROW, // int, float, etc
WIDE, // long, double
}
private int pos = -1;
private final SVType[] stack;
private final StackValueType[] stack;
public StackState(int maxStack) {
this.stack = new SVType[maxStack];
this.stack = new StackValueType[maxStack];
}
private StackState(int pos, SVType[] stack) {
private StackState(int pos, StackValueType[] stack) {
this.pos = pos;
this.stack = stack;
}
@@ -28,6 +22,15 @@ public class StackState {
return new StackState(pos, Arrays.copyOf(stack, stack.length));
}
public StackState fillFromFrame(StackFrame frame) {
int stackSize = frame.getStackSize();
this.pos = stackSize - 1;
if (stackSize > 0) {
System.arraycopy(frame.getStackValueTypes(), 0, this.stack, 0, stackSize);
}
return this;
}
public int peek() {
return pos;
}
@@ -36,15 +39,15 @@ public class StackState {
return pos - at;
}
public SVType peekTypeAt(int at) {
public StackValueType peekTypeAt(int at) {
int p = pos - at;
if (checkStackIndex(p)) {
return stack[p];
}
return SVType.NARROW;
return StackValueType.NARROW;
}
public int insert(int at, SVType type) {
public int insert(int at, StackValueType type) {
int p = pos - at;
System.arraycopy(stack, p, stack, p + 1, at);
stack[p] = type;
@@ -52,7 +55,7 @@ public class StackState {
return p;
}
public int push(SVType type) {
public int push(StackValueType type) {
int p = ++pos;
if (checkStackIndex(p)) {
stack[p] = type;
@@ -4,9 +4,9 @@ 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.attributes.stack.StackValueType;
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 {
@@ -33,32 +33,32 @@ public class LoadConstDecoder implements IJavaInsnDecoder {
case FLOAT:
insn.setLiteral(constPoolReader.readU4());
insn.setOpcode(Opcode.CONST);
state.push(0, SVType.NARROW);
state.push(0, StackValueType.NARROW);
break;
case LONG:
case DOUBLE:
insn.setLiteral(constPoolReader.readU8());
insn.setOpcode(Opcode.CONST_WIDE);
state.push(0, SVType.WIDE);
state.push(0, StackValueType.WIDE);
break;
case STRING:
insn.setIndex(constPoolReader.readU2());
insn.setOpcode(Opcode.CONST_STRING);
state.push(0, SVType.NARROW);
state.push(0, StackValueType.NARROW);
break;
case UTF8:
insn.setIndex(index);
insn.setOpcode(Opcode.CONST_STRING);
state.push(0, SVType.NARROW);
state.push(0, StackValueType.NARROW);
break;
case CLASS:
insn.setIndex(index);
insn.setOpcode(Opcode.CONST_CLASS);
state.push(0, SVType.NARROW);
state.push(0, StackValueType.NARROW);
break;
default:
@@ -1,6 +1,5 @@
package jadx.plugins.input.java.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -22,8 +21,7 @@ public class DisasmUtils {
}
private static String useRaung(byte[] bytes) {
return RaungDisasm.create()
.executeForInputStream(new ByteArrayInputStream(bytes));
return RaungDisasm.create().executeForBytes(bytes);
}
/**
@@ -30,7 +30,7 @@ public class RaungConvert implements Closeable {
this.tmpJar = Files.createTempFile("jadx-raung-", ".jar");
RaungAsm.create()
.output(tmpJar)
.inputs(input)
.inputs(raungInputs)
.execute();
return true;
} catch (Exception e) {