feat: initial support for 'invoke-custom' instruction (#384)

This commit is contained in:
Skylot
2021-01-14 20:01:53 +00:00
parent 778106c41b
commit 3dfaec5033
31 changed files with 779 additions and 34 deletions
@@ -254,7 +254,7 @@ public class ClassGen {
private void addInnerClsAndMethods(CodeWriter clsCode) {
Stream.of(cls.getInnerClasses(), cls.getMethods())
.flatMap(Collection::stream)
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
.filter(node -> !node.contains(AFlag.DONT_GENERATE) || fallback)
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
.forEach(node -> {
if (node instanceof ClassNode) {
@@ -30,6 +30,7 @@ import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeCustomNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
@@ -700,11 +701,15 @@ public class InsnGen {
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
InvokeType type = insn.getInvokeType();
if (type == InvokeType.CUSTOM) {
makeInvokeLambda(code, (InvokeCustomNode) insn);
return;
}
MethodInfo callMth = insn.getCallMth();
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
int k = 0;
InvokeType type = insn.getInvokeType();
switch (type) {
case DIRECT:
case VIRTUAL:
@@ -746,6 +751,91 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode);
}
private void makeInvokeLambda(CodeWriter code, InvokeCustomNode customNode) throws CodegenException {
if (fallback || !customNode.isInlineInsn()) {
makeSimpleLambda(code, customNode);
return;
}
MethodNode callMth = (MethodNode) customNode.getCallInsn().get(AType.METHOD_DETAILS);
makeInlinedLambdaMethod(code, customNode, callMth);
}
private void makeSimpleLambda(CodeWriter code, InvokeCustomNode customNode) {
try {
InsnNode callInsn = customNode.getCallInsn();
MethodInfo implMthInfo = customNode.getImplMthInfo();
int implArgsCount = implMthInfo.getArgsCount();
if (implArgsCount == 0) {
code.add("()");
} else {
code.add('(');
// rename lambda args
int callArgsCount = callInsn.getArgsCount();
int startArg = callArgsCount - implArgsCount;
if (startArg < 0) {
System.out.println();
}
for (int i = startArg; i < callArgsCount; i++) {
if (i != startArg) {
code.add(", ");
}
addArg(code, callInsn.getArg(i));
}
code.add(')');
}
code.add(" -> {");
if (fallback) {
code.add(" // ").add(implMthInfo.toString());
}
code.incIndent();
code.startLine();
if (!implMthInfo.getReturnType().isVoid()) {
code.add("return ");
}
makeInsn(callInsn, code, Flags.INLINE);
code.add(";");
code.decIndent();
code.startLine('}');
} catch (Exception e) {
throw new JadxRuntimeException("Failed to generate 'invoke-custom' instruction: " + e.getMessage(), e);
}
}
private void makeInlinedLambdaMethod(CodeWriter code, InvokeCustomNode customNode, MethodNode callMth) throws CodegenException {
MethodGen callMthGen = new MethodGen(mgen.getClassGen(), callMth);
NameGen nameGen = callMthGen.getNameGen();
nameGen.inheritUsedNames(this.mgen.getNameGen());
List<ArgType> implArgs = customNode.getImplMthInfo().getArgumentsTypes();
List<RegisterArg> callArgs = callMth.getArgRegs();
if (implArgs.isEmpty()) {
code.add("()");
} else {
int callArgsCount = callArgs.size();
int startArg = callArgsCount - implArgs.size();
for (int i = startArg; i < callArgsCount; i++) {
if (i != startArg) {
code.add(", ");
}
CodeVar argCodeVar = callArgs.get(i).getSVar().getCodeVar();
code.add(nameGen.assignArg(argCodeVar));
}
}
// force set external arg names into call method args
int extArgsCount = customNode.getArgsCount();
for (int i = 0; i < extArgsCount; i++) {
RegisterArg extArg = (RegisterArg) customNode.getArg(i);
callArgs.get(i).setName(extArg.getName());
}
code.add(" -> {");
code.incIndent();
callMthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
@Nullable
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
@@ -59,6 +59,10 @@ public class NameGen {
addNamesUsedInClass();
}
public void inheritUsedNames(NameGen otherNameGen) {
varNames.addAll(otherNameGen.varNames);
}
private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) {
@@ -5,6 +5,7 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
@@ -52,6 +53,12 @@ public final class MethodInfo implements Comparable<MethodInfo> {
return root.getInfoStorage().putMethod(newMth);
}
public static MethodInfo fromMethodProto(RootNode root, ClassInfo declClass, String name, IMethodProto proto) {
List<ArgType> args = Utils.collectionMap(proto.getArgTypes(), ArgType::parse);
ArgType returnType = ArgType.parse(proto.getReturnType());
return fromDetails(root, declClass, name, args, returnType);
}
public String makeSignature(boolean includeRetType) {
return makeSignature(false, includeRetType);
}
@@ -418,6 +418,8 @@ public class InsnDecoder {
return invoke(insn, InvokeType.SUPER, false);
case INVOKE_VIRTUAL:
return invoke(insn, InvokeType.VIRTUAL, false);
case INVOKE_CUSTOM:
return invoke(insn, InvokeType.CUSTOM, false);
case INVOKE_DIRECT_RANGE:
return invoke(insn, InvokeType.DIRECT, true);
@@ -427,6 +429,8 @@ public class InsnDecoder {
return invoke(insn, InvokeType.SUPER, true);
case INVOKE_VIRTUAL_RANGE:
return invoke(insn, InvokeType.VIRTUAL, true);
case INVOKE_CUSTOM_RANGE:
return invoke(insn, InvokeType.CUSTOM, true);
case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType());
@@ -524,6 +528,9 @@ public class InsnDecoder {
}
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
if (type == InvokeType.CUSTOM) {
return InvokeCustomBuilder.build(method, insn, isRange);
}
MethodInfo mthInfo = MethodInfo.fromRef(root, insn.getIndexAsMethod());
return new InvokeNode(mthInfo, insn, type, isRange);
}
@@ -0,0 +1,128 @@
package jadx.core.dex.instructions;
import java.util.List;
import jadx.api.plugins.input.data.ICallSite;
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.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class InvokeCustomBuilder {
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange) {
try {
ICallSite callSite = insn.getIndexAsCallSite();
callSite.load();
List<EncodedValue> values = callSite.getValues();
if (!checkLinkerMethod(values)) {
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite);
}
IMethodHandle callMthHandle = (IMethodHandle) values.get(4).getValue();
MethodHandleType methodHandleType = callMthHandle.getType();
if (methodHandleType.isField()) {
throw new JadxRuntimeException("Not yet supported");
}
RootNode root = mth.root();
IMethodProto lambdaProto = (IMethodProto) values.get(2).getValue();
MethodInfo lambdaInfo = MethodInfo.fromMethodProto(root, mth.getParentClass().getClassInfo(), "", lambdaProto);
InvokeCustomNode invokeCustomNode = new InvokeCustomNode(lambdaInfo, insn, false, isRange);
invokeCustomNode.setHandleType(methodHandleType);
ClassInfo implCls = ClassInfo.fromType(root, lambdaInfo.getReturnType());
String implName = (String) values.get(1).getValue();
IMethodProto implProto = (IMethodProto) values.get(3).getValue();
invokeCustomNode.setImplMthInfo(MethodInfo.fromMethodProto(root, implCls, implName, implProto));
MethodInfo callMthInfo = MethodInfo.fromRef(root, callMthHandle.getMethodRef());
InvokeType invokeType = convertInvokeType(methodHandleType);
int callArgsCount = callMthInfo.getArgsCount();
InvokeNode callInsn = new InvokeNode(callMthInfo, invokeType, callArgsCount);
invokeCustomNode.setCallInsn(callInsn);
// copy insn args
int argsCount = invokeCustomNode.getArgsCount();
for (int i = 0; i < argsCount; i++) {
InsnArg arg = invokeCustomNode.getArg(i);
callInsn.addArg(arg.duplicate());
}
if (callArgsCount > argsCount) {
// fill remaining args with NamedArg
for (int i = argsCount; i < callArgsCount; i++) {
ArgType argType = callMthInfo.getArgumentsTypes().get(i);
callInsn.addArg(new NamedArg("v" + i, argType));
}
}
MethodNode callMth = root.resolveMethod(callMthInfo);
if (callMth != null) {
callInsn.addAttr(callMth);
if (callMth.getAccessFlags().isSynthetic()
&& callMth.getUseIn().size() <= 1
&& callMth.getParentClass().equals(mth.getParentClass())) {
// inline only synthetic methods from same class
callMth.add(AFlag.DONT_GENERATE);
invokeCustomNode.setInlineInsn(true);
}
}
// prevent args inlining into not generated invoke custom node
for (InsnArg arg : invokeCustomNode.getArguments()) {
arg.add(AFlag.DONT_INLINE);
}
return invokeCustomNode;
} catch (Exception e) {
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
}
}
/**
* Expect LambdaMetafactory.metafactory method
*/
private static boolean checkLinkerMethod(List<EncodedValue> values) {
if (values.size() < 6) {
return false;
}
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
return false;
}
IMethodRef methodRef = methodHandle.getMethodRef();
if (!methodRef.getName().equals("metafactory")) {
return false;
}
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
return false;
}
return true;
}
private static InvokeType convertInvokeType(MethodHandleType type) {
switch (type) {
case INVOKE_STATIC:
return InvokeType.STATIC;
case INVOKE_INSTANCE:
return InvokeType.VIRTUAL;
case INVOKE_DIRECT:
return InvokeType.DIRECT;
case INVOKE_INTERFACE:
return InvokeType.INTERFACE;
default:
throw new JadxRuntimeException("Unsupported method handle type: " + type);
}
}
}
@@ -0,0 +1,90 @@
package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class InvokeCustomNode extends InvokeNode {
private MethodInfo implMthInfo;
private MethodHandleType handleType;
private InsnNode callInsn;
private boolean inlineInsn;
public InvokeCustomNode(MethodInfo lambdaInfo, InsnData insn, boolean instanceCall, boolean isRange) {
super(lambdaInfo, insn, InvokeType.CUSTOM, instanceCall, isRange);
}
public MethodInfo getImplMthInfo() {
return implMthInfo;
}
public void setImplMthInfo(MethodInfo implMthInfo) {
this.implMthInfo = implMthInfo;
}
public MethodHandleType getHandleType() {
return handleType;
}
public void setHandleType(MethodHandleType handleType) {
this.handleType = handleType;
}
public InsnNode getCallInsn() {
return callInsn;
}
public void setCallInsn(InsnNode callInsn) {
this.callInsn = callInsn;
}
public boolean isInlineInsn() {
return inlineInsn;
}
public void setInlineInsn(boolean inlineInsn) {
this.inlineInsn = inlineInsn;
}
@Nullable
public BaseInvokeNode getInvokeCall() {
if (callInsn.getType() == InsnType.INVOKE) {
return (BaseInvokeNode) callInsn;
}
return null;
}
@Override
public @Nullable InsnArg getInstanceArg() {
return null;
}
@Override
public boolean isStaticCall() {
return true;
}
@Override
public int getFirstArgOffset() {
return 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
if (getResult() != null) {
sb.append(getResult()).append(" = ");
}
appendArgs(sb);
sb.append("\n handle type: ").append(handleType);
sb.append("\n lambda: ").append(implMthInfo);
sb.append("\n call insn: ").append(callInsn);
return sb.toString();
}
}
@@ -8,18 +8,22 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
public final class InvokeNode extends BaseInvokeNode {
public class InvokeNode extends BaseInvokeNode {
private final InvokeType type;
private final MethodInfo mth;
public InvokeNode(MethodInfo mth, InsnData insn, InvokeType type, boolean isRange) {
super(InsnType.INVOKE, mth.getArgsCount() + (type == InvokeType.STATIC ? 0 : 1));
public InvokeNode(MethodInfo mthInfo, InsnData insn, InvokeType invokeType, boolean isRange) {
this(mthInfo, insn, invokeType, invokeType != InvokeType.STATIC, isRange);
}
public InvokeNode(MethodInfo mth, InsnData insn, InvokeType type, boolean instanceCall, boolean isRange) {
super(InsnType.INVOKE, mth.getArgsCount() + (instanceCall ? 1 : 0));
this.mth = mth;
this.type = type;
int k = isRange ? insn.getReg(0) : 0;
if (type != InvokeType.STATIC) {
if (instanceCall) {
int r = isRange ? k : insn.getReg(k);
addReg(r, mth.getDeclClass().getType());
k++;
@@ -6,4 +6,6 @@ public enum InvokeType {
VIRTUAL,
INTERFACE,
SUPER,
POLYMORPHIC,
CUSTOM,
}
@@ -815,6 +815,10 @@ public abstract class ArgType {
return false;
}
public boolean isVoid() {
return isPrimitive() && getPrimitiveType() == PrimitiveType.VOID;
}
/**
* Recursively visit all subtypes of this type.
* To exit return non-null value.
@@ -139,7 +139,7 @@ public class ClassModifier extends AbstractVisitor {
}
private static void removeSyntheticMethods(MethodNode mth) {
if (mth.isNoCode()) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return;
}
AccessInfo af = mth.getAccessFlags();
@@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
@@ -49,11 +48,11 @@ public class InlineMethods extends AbstractVisitor {
}
private void processInvokeInsn(MethodNode mth, BlockNode block, InvokeNode insn) {
MethodInfo callMthInfo = insn.getCallMth();
MethodNode callMth = mth.root().deepResolveMethod(callMthInfo);
if (callMth == null) {
IMethodDetails callMthDetails = insn.get(AType.METHOD_DETAILS);
if (!(callMthDetails instanceof MethodNode)) {
return;
}
MethodNode callMth = (MethodNode) callMthDetails;
try {
// TODO: sort inner classes process order by dependencies!
MethodInlineAttr mia = MarkMethodsForInline.process(callMth);
@@ -67,7 +66,7 @@ public class InlineMethods extends AbstractVisitor {
}
inlineMethod(mth, callMth, mia, block, insn);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to process method for inline: " + callMthInfo, e);
throw new JadxRuntimeException("Failed to process method for inline: " + callMth, e);
}
}
@@ -76,6 +76,9 @@ public class CodeShrinkVisitor extends AbstractVisitor {
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
if (arg.contains(AFlag.DONT_INLINE)) {
return;
}
SSAVar sVar = arg.getSVar();
if (sVar == null || sVar.getAssign().contains(AFlag.DONT_INLINE)) {
return;
@@ -0,0 +1,78 @@
package jadx.tests.integration.java8;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestLambdaStatic extends IntegrationTest {
public static class TestCls {
public Callable<String> test1() {
return () -> "test";
}
public Callable<String> test2(String str) {
return () -> str;
}
public Function<String, Integer> test3(String a) {
return (b) -> Integer.parseInt(a) - Integer.parseInt(b);
}
public Function<String, Integer> test4() {
return Integer::parseInt;
}
@SuppressWarnings("Convert2MethodRef")
public Function<String, Integer> test4a() {
return s -> Integer.parseInt(s);
}
public Function<String, Integer> test5() {
String str = Integer.toString(3);
return (s) -> Integer.parseInt(str) - Integer.parseInt(s);
}
public void check() throws Exception {
assertThat(test1().call()).isEqualTo("test");
assertThat(test2("a").call()).isEqualTo("a");
assertThat(test3("3").apply("1")).isEqualTo(2);
assertThat(test4().apply("5")).isEqualTo(5);
assertThat(test4a().apply("7")).isEqualTo(7);
assertThat(test5().apply("1")).isEqualTo(2);
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.doesNotContain("lambda$")
.doesNotContain("renamed")
.containsLines(2,
"return () -> {",
indent() + "return \"test\";",
"};")
.containsLines(2,
"return () -> {",
indent() + "return str;",
"};");
}
@Test
public void testNoDebug() {
noDebugInfo();
getClassNode(TestCls.class);
}
@Test
public void testFallback() {
setFallback();
getClassNode(TestCls.class);
}
}
@@ -2,6 +2,7 @@ package jadx.plugins.input.dex.insns;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.InsnData;
@@ -14,6 +15,7 @@ import jadx.plugins.input.dex.sections.SectionReader;
public class DexInsnData implements InsnData {
private final DexCodeReader codeData;
private final SectionReader externalReader;
private final SectionReader secondExtReader;
private DexInsnInfo insnInfo;
private boolean decoded;
@@ -32,6 +34,7 @@ public class DexInsnData implements InsnData {
public DexInsnData(DexCodeReader codeData, SectionReader externalReader) {
this.codeData = codeData;
this.externalReader = externalReader;
this.secondExtReader = externalReader.copy();
}
@Override
@@ -110,6 +113,11 @@ public class DexInsnData implements InsnData {
return externalReader.getMethodRef(index);
}
@Override
public ICallSite getIndexAsCallSite() {
return externalReader.getCallSite(index, secondExtReader);
}
@Nullable
@Override
public ICustomPayload getPayload() {
@@ -0,0 +1,39 @@
package jadx.plugins.input.dex.sections;
import java.util.List;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.annotations.EncodedValue;
public class DexCallSite implements ICallSite {
private final List<EncodedValue> values;
public DexCallSite(List<EncodedValue> values) {
this.values = values;
}
@Override
public List<EncodedValue> getValues() {
return values;
}
@Override
public void load() {
for (EncodedValue value : values) {
Object obj = value.getValue();
if (obj instanceof IMethodRef) {
((IMethodRef) obj).load();
} else if (obj instanceof IMethodHandle) {
((IMethodHandle) obj).load();
}
}
}
@Override
public String toString() {
return "CallSite{" + values + '}';
}
}
@@ -1,6 +1,5 @@
package jadx.plugins.input.dex.sections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -175,12 +174,7 @@ public class DexClassData implements IClassData {
return Collections.emptyList();
}
in.absPos(staticValuesOff);
int count = in.readUleb128();
List<EncodedValue> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(annotationsParser.parseEncodedValue(in));
}
return list;
return annotationsParser.parseEncodedArray(in);
}
@Override
@@ -16,6 +16,9 @@ public class DexHeader {
private final int methodIdsOff;
private final int methodIdsSize;
private int callSiteOff;
private int methodHandleOff;
public DexHeader(SectionReader buf) {
byte[] magic = buf.readByteArray(4);
version = buf.readString(3);
@@ -45,6 +48,28 @@ public class DexHeader {
classDefsOff = buf.readInt();
int dataSize = buf.readInt();
int dataOff = buf.readInt();
readMapList(buf, mapListOff);
}
private void readMapList(SectionReader buf, int mapListOff) {
buf.absPos(mapListOff);
int size = buf.readInt();
for (int i = 0; i < size; i++) {
int type = buf.readUShort();
buf.skip(6);
int offset = buf.readInt();
switch (type) {
case 0x0007:
callSiteOff = offset;
break;
case 0x0008:
methodHandleOff = offset;
break;
}
}
}
public String getVersion() {
@@ -94,4 +119,12 @@ public class DexHeader {
public int getMethodIdsSize() {
return methodIdsSize;
}
public int getCallSiteOff() {
return callSiteOff;
}
public int getMethodHandleOff() {
return methodHandleOff;
}
}
@@ -0,0 +1,31 @@
package jadx.plugins.input.dex.sections;
import java.util.List;
import jadx.api.plugins.input.data.IMethodProto;
import jadx.plugins.input.dex.utils.Utils;
public class DexMethodProto implements IMethodProto {
private final List<String> argTypes;
private final String returnType;
public DexMethodProto(List<String> argTypes, String returnType) {
this.returnType = returnType;
this.argTypes = argTypes;
}
@Override
public List<String> getArgTypes() {
return argTypes;
}
@Override
public String getReturnType() {
return returnType;
}
@Override
public String toString() {
return "(" + Utils.listToStr(argTypes) + ")" + returnType;
}
}
@@ -9,8 +9,14 @@ import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.impl.FieldRefHandle;
import jadx.api.plugins.input.data.impl.MethodRefHandle;
import jadx.plugins.input.dex.DexReader;
import jadx.plugins.input.dex.sections.annotations.EncodedValueParser;
import jadx.plugins.input.dex.utils.Leb128;
import jadx.plugins.input.dex.utils.MUtf8;
@@ -119,6 +125,13 @@ public class SectionReader {
return new String(readByteArray(len), StandardCharsets.US_ASCII);
}
private List<String> readTypeListAt(int paramsOff) {
if (paramsOff == 0) {
return Collections.emptyList();
}
return absPos(paramsOff).readTypeList();
}
public List<String> readTypeList() {
int size = readInt();
if (size == 0) {
@@ -180,6 +193,50 @@ public class SectionReader {
return methodRef;
}
public ICallSite getCallSite(int idx, SectionReader ext) {
int callSiteOff = dexReader.getHeader().getCallSiteOff();
absPos(callSiteOff + idx * 4);
absPos(readInt());
return new DexCallSite(EncodedValueParser.parseEncodedArray(this, ext));
}
public IMethodHandle getMethodHandle(int idx) {
int methodHandleOff = dexReader.getHeader().getMethodHandleOff();
absPos(methodHandleOff + idx * 8);
MethodHandleType handleType = getMethodHandleType(readUShort());
skip(2);
int refId = readUShort();
if (handleType.isField()) {
return new FieldRefHandle(handleType, getFieldData(refId));
}
return new MethodRefHandle(handleType, getMethodRef(refId));
}
private MethodHandleType getMethodHandleType(int type) {
switch (type) {
case 0x00:
return MethodHandleType.STATIC_PUT;
case 0x01:
return MethodHandleType.STATIC_GET;
case 0x02:
return MethodHandleType.INSTANCE_PUT;
case 0x03:
return MethodHandleType.INSTANCE_GET;
case 0x04:
return MethodHandleType.INVOKE_STATIC;
case 0x05:
return MethodHandleType.INVOKE_INSTANCE;
case 0x06:
return MethodHandleType.INVOKE_CONSTRUCTOR;
case 0x07:
return MethodHandleType.INVOKE_DIRECT;
case 0x08:
return MethodHandleType.INVOKE_INTERFACE;
default:
throw new IllegalArgumentException("Unknown method handle type: 0x" + Integer.toHexString(type));
}
}
public void initMethodRef(int idx, DexMethodRef methodRef) {
methodRef.initUniqId(dexReader, idx);
methodRef.setDexIdx(idx);
@@ -200,18 +257,22 @@ public class SectionReader {
int returnTypeIdx = readInt();
int paramsOff = readInt();
List<String> argTypes;
if (paramsOff == 0) {
argTypes = Collections.emptyList();
} else {
argTypes = absPos(paramsOff).readTypeList();
}
List<String> argTypes = readTypeListAt(paramsOff);
methodRef.setParentClassType(getType(classTypeIdx));
methodRef.setName(getString(nameIdx));
methodRef.setReturnType(getType(returnTypeIdx));
methodRef.setArgTypes(argTypes);
}
public DexMethodProto getMethodProto(int idx) {
int protoIdsOff = dexReader.getHeader().getProtoIdsOff();
absPos(protoIdsOff + idx * 12);
skip(4); // shortyIdx
int returnTypeIdx = readInt();
int paramsOff = readInt();
return new DexMethodProto(readTypeListAt(paramsOff), getType(returnTypeIdx));
}
public List<String> getMethodParamTypes(int idx) {
DexHeader header = dexReader.getHeader();
int methodIdsOff = header.getMethodIdsOff();
@@ -168,4 +168,8 @@ public class AnnotationsParser {
public EncodedValue parseEncodedValue(SectionReader in) {
return EncodedValueParser.parseValue(in, ext);
}
public List<EncodedValue> parseEncodedArray(SectionReader in) {
return EncodedValueParser.parseEncodedArray(in, ext);
}
}
@@ -17,6 +17,8 @@ public class EncodedValueParser {
private static final int ENCODED_LONG = 0x06;
private static final int ENCODED_FLOAT = 0x10;
private static final int ENCODED_DOUBLE = 0x11;
private static final int ENCODED_METHOD_TYPE = 0x15;
private static final int ENCODED_METHOD_HANDLE = 0x16;
private static final int ENCODED_STRING = 0x17;
private static final int ENCODED_TYPE = 0x18;
private static final int ENCODED_FIELD = 0x19;
@@ -62,29 +64,39 @@ public class EncodedValueParser {
case ENCODED_TYPE:
return new EncodedValue(EncodedType.ENCODED_TYPE, ext.getType(parseUnsignedInt(in, size)));
case ENCODED_METHOD:
return new EncodedValue(EncodedType.ENCODED_METHOD, ext.getMethodRef(parseUnsignedInt(in, size)));
case ENCODED_FIELD:
case ENCODED_ENUM:
return new EncodedValue(EncodedType.ENCODED_FIELD, ext.getFieldData(parseUnsignedInt(in, size)));
case ENCODED_ARRAY:
int count = in.readUleb128();
List<EncodedValue> values = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue(in, ext));
}
return new EncodedValue(EncodedType.ENCODED_ARRAY, values);
return new EncodedValue(EncodedType.ENCODED_ARRAY, parseEncodedArray(in, ext));
case ENCODED_ANNOTATION:
return new EncodedValue(EncodedType.ENCODED_ANNOTATION, AnnotationsParser.readAnnotation(in, ext, false));
case ENCODED_METHOD:
return new EncodedValue(EncodedType.ENCODED_METHOD, ext.getMethodRef(parseUnsignedInt(in, size)));
case ENCODED_METHOD_TYPE:
return new EncodedValue(EncodedType.ENCODED_METHOD_TYPE, ext.getMethodProto(parseUnsignedInt(in, size)));
case ENCODED_METHOD_HANDLE:
return new EncodedValue(EncodedType.ENCODED_METHOD_HANDLE, ext.getMethodHandle(parseUnsignedInt(in, size)));
default:
throw new DexException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
}
public static List<EncodedValue> parseEncodedArray(SectionReader in, SectionReader ext) {
int count = in.readUleb128();
List<EncodedValue> values = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue(in, ext));
}
return values;
}
private static int parseUnsignedInt(SectionReader in, int byteCount) {
return (int) parseNumber(in, byteCount, false, 0);
}
@@ -9,6 +9,9 @@ public class Utils {
if (collection == null) {
return "null";
}
if (collection.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
Iterator<T> it = collection.iterator();
if (it.hasNext()) {
@@ -0,0 +1,12 @@
package jadx.api.plugins.input.data;
import java.util.List;
import jadx.api.plugins.input.data.annotations.EncodedValue;
public interface ICallSite {
List<EncodedValue> getValues();
void load();
}
@@ -0,0 +1,12 @@
package jadx.api.plugins.input.data;
public interface IMethodHandle {
MethodHandleType getType();
IFieldData getFieldRef();
IMethodRef getMethodRef();
void load();
}
@@ -0,0 +1,10 @@
package jadx.api.plugins.input.data;
import java.util.List;
public interface IMethodProto {
String getReturnType();
List<String> getArgTypes();
}
@@ -0,0 +1,26 @@
package jadx.api.plugins.input.data;
public enum MethodHandleType {
STATIC_PUT,
STATIC_GET,
INSTANCE_PUT,
INSTANCE_GET,
INVOKE_STATIC,
INVOKE_INSTANCE,
INVOKE_DIRECT,
INVOKE_CONSTRUCTOR,
INVOKE_INTERFACE;
public boolean isField() {
switch (this) {
case STATIC_PUT:
case STATIC_GET:
case INSTANCE_PUT:
case INSTANCE_GET:
return true;
default:
return false;
}
}
}
@@ -15,6 +15,8 @@ public enum EncodedType {
ENCODED_ENUM,
ENCODED_FIELD,
ENCODED_METHOD,
ENCODED_METHOD_TYPE,
ENCODED_METHOD_HANDLE,
ENCODED_ARRAY,
ENCODED_ANNOTATION
}
@@ -0,0 +1,37 @@
package jadx.api.plugins.input.data.impl;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.MethodHandleType;
public class FieldRefHandle implements IMethodHandle {
private final IFieldData fieldRef;
private final MethodHandleType type;
public FieldRefHandle(MethodHandleType type, IFieldData fieldRef) {
this.fieldRef = fieldRef;
this.type = type;
}
@Override
public MethodHandleType getType() {
return type;
}
@Override
public IFieldData getFieldRef() {
return fieldRef;
}
@Override
public IMethodRef getMethodRef() {
return null;
}
@Override
public void load() {
// already loaded
}
}
@@ -0,0 +1,42 @@
package jadx.api.plugins.input.data.impl;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodHandle;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.MethodHandleType;
public class MethodRefHandle implements IMethodHandle {
private final IMethodRef methodRef;
private final MethodHandleType type;
public MethodRefHandle(MethodHandleType type, IMethodRef methodRef) {
this.methodRef = methodRef;
this.type = type;
}
@Override
public MethodHandleType getType() {
return type;
}
@Override
public IMethodRef getMethodRef() {
return methodRef;
}
@Override
public IFieldData getFieldRef() {
return null;
}
@Override
public void load() {
methodRef.load();
}
@Override
public String toString() {
return type + ": " + methodRef;
}
}
@@ -2,6 +2,7 @@ package jadx.api.plugins.input.insns;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
@@ -36,6 +37,8 @@ public interface InsnData {
IMethodRef getIndexAsMethod();
ICallSite getIndexAsCallSite();
@Nullable
ICustomPayload getPayload();
}