Skip to content

Commit 85c2c63

Browse files
committed
fix: output unknown invoke-custom as polymorphic call (#1760)
1 parent f354f7d commit 85c2c63

14 files changed

Lines changed: 541 additions & 48 deletions

File tree

jadx-core/src/main/java/jadx/core/codegen/InsnGen.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import jadx.api.metadata.annotations.InsnCodeOffset;
1515
import jadx.api.metadata.annotations.VarNode;
1616
import jadx.api.plugins.input.data.MethodHandleType;
17+
import jadx.api.plugins.input.data.annotations.EncodedValue;
1718
import jadx.core.dex.attributes.AFlag;
1819
import jadx.core.dex.attributes.AType;
1920
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -36,6 +37,7 @@
3637
import jadx.core.dex.instructions.IndexInsnNode;
3738
import jadx.core.dex.instructions.InsnType;
3839
import jadx.core.dex.instructions.InvokeCustomNode;
40+
import jadx.core.dex.instructions.InvokeCustomRawNode;
3941
import jadx.core.dex.instructions.InvokeNode;
4042
import jadx.core.dex.instructions.InvokeType;
4143
import jadx.core.dex.instructions.NewArrayNode;
@@ -795,6 +797,10 @@ private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenExcepti
795797
MethodInfo callMth = insn.getCallMth();
796798
MethodNode callMthNode = mth.root().resolveMethod(callMth);
797799

800+
if (type == InvokeType.CUSTOM_RAW) {
801+
makeInvokeCustomRaw((InvokeCustomRawNode) insn, callMthNode, code);
802+
return;
803+
}
798804
if (insn.isPolymorphicCall()) {
799805
// add missing cast
800806
code.add('(');
@@ -845,6 +851,31 @@ private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenExcepti
845851
generateMethodArguments(code, insn, k, callMthNode);
846852
}
847853

854+
private void makeInvokeCustomRaw(InvokeCustomRawNode insn,
855+
@Nullable MethodNode callMthNode, ICodeWriter code) throws CodegenException {
856+
if (isFallback()) {
857+
code.add("call_site(");
858+
code.incIndent();
859+
for (EncodedValue value : insn.getCallSiteValues()) {
860+
code.startLine(value.toString());
861+
}
862+
code.decIndent();
863+
code.startLine(").invoke");
864+
generateMethodArguments(code, insn, 0, callMthNode);
865+
} else {
866+
ArgType returnType = insn.getCallMth().getReturnType();
867+
if (!returnType.isVoid()) {
868+
code.add('(');
869+
useType(code, returnType);
870+
code.add(") ");
871+
}
872+
makeInvoke(insn.getResolveInvoke(), code);
873+
code.add(".dynamicInvoker().invoke");
874+
generateMethodArguments(code, insn, 0, callMthNode);
875+
code.add(" /* invoke-custom */");
876+
}
877+
}
878+
848879
// FIXME: add 'this' for equals methods in scope
849880
private boolean needInvokeArg(InsnArg arg) {
850881
if (arg.isAnyThis()) {

jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
import jadx.api.plugins.input.data.ICallSite;
66
import jadx.api.plugins.input.data.annotations.EncodedValue;
77
import jadx.api.plugins.input.insns.InsnData;
8+
import jadx.core.dex.attributes.AFlag;
9+
import jadx.core.dex.attributes.AType;
10+
import jadx.core.dex.attributes.nodes.JadxError;
811
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
12+
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
913
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
1014
import jadx.core.dex.nodes.InsnNode;
1115
import jadx.core.dex.nodes.MethodNode;
16+
import jadx.core.utils.Utils;
1217
import jadx.core.utils.exceptions.JadxRuntimeException;
1318
import jadx.core.utils.input.InsnDataUtils;
1419

@@ -28,8 +33,16 @@ public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange) {
2833
if (CustomStringConcat.isStringConcat(values)) {
2934
return CustomStringConcat.buildStringConcat(insn, isRange, values);
3035
}
31-
// TODO: output raw dynamic call
32-
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite);
36+
try {
37+
return CustomRawCall.build(mth, insn, isRange, values);
38+
} catch (Exception e) {
39+
mth.addWarn("Failed to decode invoke-custom: \n" + Utils.listToString(values, "\n")
40+
+ ",\n exception: " + Utils.getStackTrace(e));
41+
InsnNode nop = new InsnNode(InsnType.NOP, 0);
42+
nop.add(AFlag.SYNTHETIC);
43+
nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to decode invoke-custom: " + values, e));
44+
return nop;
45+
}
3346
} catch (Exception e) {
3447
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
3548
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package jadx.core.dex.instructions;
2+
3+
import java.util.List;
4+
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import jadx.api.plugins.input.data.annotations.EncodedValue;
8+
import jadx.api.plugins.input.insns.InsnData;
9+
import jadx.core.dex.info.MethodInfo;
10+
import jadx.core.dex.instructions.args.InsnArg;
11+
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
12+
import jadx.core.dex.nodes.InsnNode;
13+
import jadx.core.utils.InsnUtils;
14+
import jadx.core.utils.Utils;
15+
16+
/**
17+
* Information for raw invoke-custom instruction.<br>
18+
* Output will be formatted as polymorphic call with equivalent semantic
19+
* Contains two parts:
20+
* - resolve: treated as additional invoke insn (uses only constant args)
21+
* - invoke: call of resolved method (base for this invoke)
22+
* <br>
23+
* See {@link CustomRawCall} class for build details
24+
*/
25+
public class InvokeCustomRawNode extends InvokeNode {
26+
private final InvokeNode resolve;
27+
private List<EncodedValue> callSiteValues;
28+
29+
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InsnData insn, boolean isRange) {
30+
super(mthInfo, insn, InvokeType.CUSTOM_RAW, false, isRange);
31+
this.resolve = resolve;
32+
}
33+
34+
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InvokeType invokeType, int argsCount) {
35+
super(mthInfo, invokeType, argsCount);
36+
this.resolve = resolve;
37+
}
38+
39+
public InvokeNode getResolveInvoke() {
40+
return resolve;
41+
}
42+
43+
public void setCallSiteValues(List<EncodedValue> callSiteValues) {
44+
this.callSiteValues = callSiteValues;
45+
}
46+
47+
public List<EncodedValue> getCallSiteValues() {
48+
return callSiteValues;
49+
}
50+
51+
@Override
52+
public InsnNode copy() {
53+
InvokeCustomRawNode copy = new InvokeCustomRawNode(resolve, getCallMth(), getInvokeType(), getArgsCount());
54+
copyCommonParams(copy);
55+
copy.setCallSiteValues(callSiteValues);
56+
return copy;
57+
}
58+
59+
@Override
60+
public boolean isStaticCall() {
61+
return true;
62+
}
63+
64+
@Override
65+
public int getFirstArgOffset() {
66+
return 0;
67+
}
68+
69+
@Override
70+
public @Nullable InsnArg getInstanceArg() {
71+
return null;
72+
}
73+
74+
@Override
75+
public boolean isSame(InsnNode obj) {
76+
if (this == obj) {
77+
return true;
78+
}
79+
if (obj instanceof InvokeCustomRawNode) {
80+
return super.isSame(obj) && resolve.isSame(((InvokeCustomRawNode) obj).resolve);
81+
}
82+
return false;
83+
}
84+
85+
@Override
86+
public String toString() {
87+
StringBuilder sb = new StringBuilder();
88+
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
89+
if (getResult() != null) {
90+
sb.append(getResult()).append(" = ");
91+
}
92+
if (!appendArgs(sb)) {
93+
sb.append('\n');
94+
}
95+
sb.append(" call-site: \n ").append(Utils.listToString(callSiteValues, "\n ")).append('\n');
96+
return sb.toString();
97+
}
98+
}

jadx-core/src/main/java/jadx/core/dex/instructions/InvokeType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public enum InvokeType {
88
SUPER,
99
POLYMORPHIC,
1010
CUSTOM,
11+
CUSTOM_RAW,
1112
}

jadx-core/src/main/java/jadx/core/dex/instructions/args/PrimitiveType.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
package jadx.core.dex.instructions.args;
22

33
public enum PrimitiveType {
4-
BOOLEAN("Z", "boolean"),
5-
CHAR("C", "char"),
6-
BYTE("B", "byte"),
7-
SHORT("S", "short"),
8-
INT("I", "int"),
9-
FLOAT("F", "float"),
10-
LONG("J", "long"),
11-
DOUBLE("D", "double"),
12-
OBJECT("L", "OBJECT"),
13-
ARRAY("[", "ARRAY"),
14-
VOID("V", "void");
4+
BOOLEAN("Z", "boolean", ArgType.object("java.lang.Boolean")),
5+
CHAR("C", "char", ArgType.object("java.lang.Character")),
6+
BYTE("B", "byte", ArgType.object("java.lang.Byte")),
7+
SHORT("S", "short", ArgType.object("java.lang.Short")),
8+
INT("I", "int", ArgType.object("java.lang.Integer")),
9+
FLOAT("F", "float", ArgType.object("java.lang.Float")),
10+
LONG("J", "long", ArgType.object("java.lang.Long")),
11+
DOUBLE("D", "double", ArgType.object("java.lang.Double")),
12+
OBJECT("L", "OBJECT", ArgType.OBJECT),
13+
ARRAY("[", "ARRAY", ArgType.OBJECT_ARRAY),
14+
VOID("V", "void", ArgType.object("java.lang.Void"));
1515

1616
private final String shortName;
1717
private final String longName;
18+
private final ArgType boxType;
1819

19-
PrimitiveType(String shortName, String longName) {
20+
PrimitiveType(String shortName, String longName, ArgType boxType) {
2021
this.shortName = shortName;
2122
this.longName = longName;
23+
this.boxType = boxType;
2224
}
2325

2426
public String getShortName() {
@@ -29,6 +31,10 @@ public String getLongName() {
2931
return longName;
3032
}
3133

34+
public ArgType getBoxType() {
35+
return boxType;
36+
}
37+
3238
@Override
3339
public String toString() {
3440
return longName;

jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomLambdaCall.java

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import jadx.api.plugins.input.data.IMethodProto;
99
import jadx.api.plugins.input.data.IMethodRef;
1010
import jadx.api.plugins.input.data.MethodHandleType;
11+
import jadx.api.plugins.input.data.annotations.EncodedType;
1112
import jadx.api.plugins.input.data.annotations.EncodedValue;
1213
import jadx.api.plugins.input.insns.InsnData;
1314
import jadx.core.dex.attributes.AFlag;
@@ -34,7 +35,11 @@ public static boolean isLambdaInvoke(List<EncodedValue> values) {
3435
if (values.size() < 6) {
3536
return false;
3637
}
37-
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
38+
EncodedValue mthRef = values.get(0);
39+
if (mthRef.getType() != EncodedType.ENCODED_METHOD_HANDLE) {
40+
return false;
41+
}
42+
IMethodHandle methodHandle = (IMethodHandle) mthRef.getValue();
3843
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
3944
return false;
4045
}
@@ -113,7 +118,7 @@ private static InvokeCustomNode buildMethodCall(MethodNode mth, InsnData insn, b
113118
@NotNull
114119
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
115120
MethodInfo callMthInfo) {
116-
InvokeType invokeType = convertInvokeType(methodHandleType);
121+
InvokeType invokeType = InvokeCustomUtils.convertInvokeType(methodHandleType);
117122
int callArgsCount = callMthInfo.getArgsCount();
118123
boolean instanceCall = invokeType != InvokeType.STATIC;
119124
if (instanceCall) {
@@ -147,21 +152,4 @@ private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, Inv
147152
}
148153
return invokeNode;
149154
}
150-
151-
private static InvokeType convertInvokeType(MethodHandleType type) {
152-
switch (type) {
153-
case INVOKE_STATIC:
154-
return InvokeType.STATIC;
155-
case INVOKE_INSTANCE:
156-
return InvokeType.VIRTUAL;
157-
case INVOKE_DIRECT:
158-
case INVOKE_CONSTRUCTOR:
159-
return InvokeType.DIRECT;
160-
case INVOKE_INTERFACE:
161-
return InvokeType.INTERFACE;
162-
163-
default:
164-
throw new JadxRuntimeException("Unsupported method handle type: " + type);
165-
}
166-
}
167155
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package jadx.core.dex.instructions.invokedynamic;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import jadx.api.plugins.input.data.IMethodHandle;
7+
import jadx.api.plugins.input.data.IMethodProto;
8+
import jadx.api.plugins.input.data.annotations.EncodedValue;
9+
import jadx.api.plugins.input.insns.InsnData;
10+
import jadx.core.dex.info.ClassInfo;
11+
import jadx.core.dex.info.MethodInfo;
12+
import jadx.core.dex.instructions.ConstStringNode;
13+
import jadx.core.dex.instructions.InvokeCustomRawNode;
14+
import jadx.core.dex.instructions.InvokeNode;
15+
import jadx.core.dex.instructions.InvokeType;
16+
import jadx.core.dex.instructions.args.ArgType;
17+
import jadx.core.dex.instructions.args.InsnArg;
18+
import jadx.core.dex.nodes.InsnNode;
19+
import jadx.core.dex.nodes.MethodNode;
20+
import jadx.core.dex.nodes.RootNode;
21+
import jadx.core.utils.exceptions.JadxRuntimeException;
22+
23+
import static jadx.core.utils.EncodedValueUtils.buildLookupArg;
24+
import static jadx.core.utils.EncodedValueUtils.convertToInsnArg;
25+
26+
/**
27+
* Show `invoke-custom` similar to polymorphic call
28+
*/
29+
public class CustomRawCall {
30+
31+
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
32+
IMethodHandle resolveHandle = (IMethodHandle) values.get(0).getValue();
33+
String invokeName = (String) values.get(1).getValue();
34+
IMethodProto invokeProto = (IMethodProto) values.get(2).getValue();
35+
List<InsnArg> resolveArgs = buildArgs(mth, values);
36+
37+
if (resolveHandle.getType().isField()) {
38+
throw new JadxRuntimeException("Field handle not yet supported");
39+
}
40+
41+
RootNode root = mth.root();
42+
MethodInfo resolveMth = MethodInfo.fromRef(root, resolveHandle.getMethodRef());
43+
InvokeType resolveInvokeType = InvokeCustomUtils.convertInvokeType(resolveHandle.getType());
44+
InvokeNode resolve = new InvokeNode(resolveMth, resolveInvokeType, resolveArgs.size());
45+
resolveArgs.forEach(resolve::addArg);
46+
47+
ClassInfo invokeCls = ClassInfo.fromType(root, ArgType.OBJECT); // type will be known at runtime
48+
MethodInfo invokeMth = MethodInfo.fromMethodProto(root, invokeCls, invokeName, invokeProto);
49+
InvokeCustomRawNode customRawNode = new InvokeCustomRawNode(resolve, invokeMth, insn, isRange);
50+
customRawNode.setCallSiteValues(values);
51+
return customRawNode;
52+
}
53+
54+
private static List<InsnArg> buildArgs(MethodNode mth, List<EncodedValue> values) {
55+
int valuesCount = values.size();
56+
List<InsnArg> list = new ArrayList<>(valuesCount);
57+
RootNode root = mth.root();
58+
list.add(buildLookupArg(root)); // use `java.lang.invoke.MethodHandles.lookup()` as first arg
59+
for (int i = 1; i < valuesCount; i++) {
60+
EncodedValue value = values.get(i);
61+
try {
62+
list.add(convertToInsnArg(root, value));
63+
} catch (Exception e) {
64+
mth.addWarnComment("Failed to build arg in invoke-custom insn: " + value, e);
65+
list.add(InsnArg.wrapArg(new ConstStringNode(value.toString())));
66+
}
67+
}
68+
return list;
69+
}
70+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package jadx.core.dex.instructions.invokedynamic;
2+
3+
import jadx.api.plugins.input.data.MethodHandleType;
4+
import jadx.core.dex.instructions.InvokeType;
5+
import jadx.core.utils.exceptions.JadxRuntimeException;
6+
7+
public class InvokeCustomUtils {
8+
9+
public static InvokeType convertInvokeType(MethodHandleType type) {
10+
switch (type) {
11+
case INVOKE_STATIC:
12+
return InvokeType.STATIC;
13+
case INVOKE_INSTANCE:
14+
return InvokeType.VIRTUAL;
15+
case INVOKE_DIRECT:
16+
case INVOKE_CONSTRUCTOR:
17+
return InvokeType.DIRECT;
18+
case INVOKE_INTERFACE:
19+
return InvokeType.INTERFACE;
20+
21+
default:
22+
throw new JadxRuntimeException("Unsupported method handle type: " + type);
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)