feat: initial support for 'invoke-custom' instruction (#384)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+8
@@ -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() {
|
||||
|
||||
+39
@@ -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
-7
@@ -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
|
||||
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+67
-6
@@ -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();
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-9
@@ -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()) {
|
||||
|
||||
+12
@@ -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();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package jadx.api.plugins.input.data;
|
||||
|
||||
public interface IMethodHandle {
|
||||
|
||||
MethodHandleType getType();
|
||||
|
||||
IFieldData getFieldRef();
|
||||
|
||||
IMethodRef getMethodRef();
|
||||
|
||||
void load();
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package jadx.api.plugins.input.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IMethodProto {
|
||||
|
||||
String getReturnType();
|
||||
|
||||
List<String> getArgTypes();
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -15,6 +15,8 @@ public enum EncodedType {
|
||||
ENCODED_ENUM,
|
||||
ENCODED_FIELD,
|
||||
ENCODED_METHOD,
|
||||
ENCODED_METHOD_TYPE,
|
||||
ENCODED_METHOD_HANDLE,
|
||||
ENCODED_ARRAY,
|
||||
ENCODED_ANNOTATION
|
||||
}
|
||||
|
||||
+37
@@ -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
|
||||
}
|
||||
}
|
||||
+42
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user