fix: output unknown invoke-custom as polymorphic call (#1760)
This commit is contained in:
@@ -14,6 +14,7 @@ import jadx.api.ICodeWriter;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -36,6 +37,7 @@ 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.InvokeCustomRawNode;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
@@ -795,6 +797,10 @@ public class InsnGen {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
MethodNode callMthNode = mth.root().resolveMethod(callMth);
|
||||
|
||||
if (type == InvokeType.CUSTOM_RAW) {
|
||||
makeInvokeCustomRaw((InvokeCustomRawNode) insn, callMthNode, code);
|
||||
return;
|
||||
}
|
||||
if (insn.isPolymorphicCall()) {
|
||||
// add missing cast
|
||||
code.add('(');
|
||||
@@ -845,6 +851,31 @@ public class InsnGen {
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
private void makeInvokeCustomRaw(InvokeCustomRawNode insn,
|
||||
@Nullable MethodNode callMthNode, ICodeWriter code) throws CodegenException {
|
||||
if (isFallback()) {
|
||||
code.add("call_site(");
|
||||
code.incIndent();
|
||||
for (EncodedValue value : insn.getCallSiteValues()) {
|
||||
code.startLine(value.toString());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine(").invoke");
|
||||
generateMethodArguments(code, insn, 0, callMthNode);
|
||||
} else {
|
||||
ArgType returnType = insn.getCallMth().getReturnType();
|
||||
if (!returnType.isVoid()) {
|
||||
code.add('(');
|
||||
useType(code, returnType);
|
||||
code.add(") ");
|
||||
}
|
||||
makeInvoke(insn.getResolveInvoke(), code);
|
||||
code.add(".dynamicInvoker().invoke");
|
||||
generateMethodArguments(code, insn, 0, callMthNode);
|
||||
code.add(" /* invoke-custom */");
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: add 'this' for equals methods in scope
|
||||
private boolean needInvokeArg(InsnArg arg) {
|
||||
if (arg.isAnyThis()) {
|
||||
|
||||
@@ -5,10 +5,15 @@ import java.util.List;
|
||||
import jadx.api.plugins.input.data.ICallSite;
|
||||
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.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall;
|
||||
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||
import jadx.core.dex.instructions.invokedynamic.CustomStringConcat;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.input.InsnDataUtils;
|
||||
|
||||
@@ -28,8 +33,16 @@ public class InvokeCustomBuilder {
|
||||
if (CustomStringConcat.isStringConcat(values)) {
|
||||
return CustomStringConcat.buildStringConcat(insn, isRange, values);
|
||||
}
|
||||
// TODO: output raw dynamic call
|
||||
throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite);
|
||||
try {
|
||||
return CustomRawCall.build(mth, insn, isRange, values);
|
||||
} catch (Exception e) {
|
||||
mth.addWarn("Failed to decode invoke-custom: \n" + Utils.listToString(values, "\n")
|
||||
+ ",\n exception: " + Utils.getStackTrace(e));
|
||||
InsnNode nop = new InsnNode(InsnType.NOP, 0);
|
||||
nop.add(AFlag.SYNTHETIC);
|
||||
nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to decode invoke-custom: " + values, e));
|
||||
return nop;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.invokedynamic.CustomRawCall;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Information for raw invoke-custom instruction.<br>
|
||||
* Output will be formatted as polymorphic call with equivalent semantic
|
||||
* Contains two parts:
|
||||
* - resolve: treated as additional invoke insn (uses only constant args)
|
||||
* - invoke: call of resolved method (base for this invoke)
|
||||
* <br>
|
||||
* See {@link CustomRawCall} class for build details
|
||||
*/
|
||||
public class InvokeCustomRawNode extends InvokeNode {
|
||||
private final InvokeNode resolve;
|
||||
private List<EncodedValue> callSiteValues;
|
||||
|
||||
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InsnData insn, boolean isRange) {
|
||||
super(mthInfo, insn, InvokeType.CUSTOM_RAW, false, isRange);
|
||||
this.resolve = resolve;
|
||||
}
|
||||
|
||||
public InvokeCustomRawNode(InvokeNode resolve, MethodInfo mthInfo, InvokeType invokeType, int argsCount) {
|
||||
super(mthInfo, invokeType, argsCount);
|
||||
this.resolve = resolve;
|
||||
}
|
||||
|
||||
public InvokeNode getResolveInvoke() {
|
||||
return resolve;
|
||||
}
|
||||
|
||||
public void setCallSiteValues(List<EncodedValue> callSiteValues) {
|
||||
this.callSiteValues = callSiteValues;
|
||||
}
|
||||
|
||||
public List<EncodedValue> getCallSiteValues() {
|
||||
return callSiteValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
InvokeCustomRawNode copy = new InvokeCustomRawNode(resolve, getCallMth(), getInvokeType(), getArgsCount());
|
||||
copyCommonParams(copy);
|
||||
copy.setCallSiteValues(callSiteValues);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaticCall() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstArgOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InsnArg getInstanceArg() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof InvokeCustomRawNode) {
|
||||
return super.isSame(obj) && resolve.isSame(((InvokeCustomRawNode) obj).resolve);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(InsnUtils.formatOffset(offset)).append(": INVOKE_CUSTOM ");
|
||||
if (getResult() != null) {
|
||||
sb.append(getResult()).append(" = ");
|
||||
}
|
||||
if (!appendArgs(sb)) {
|
||||
sb.append('\n');
|
||||
}
|
||||
sb.append(" call-site: \n ").append(Utils.listToString(callSiteValues, "\n ")).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,5 @@ public enum InvokeType {
|
||||
SUPER,
|
||||
POLYMORPHIC,
|
||||
CUSTOM,
|
||||
CUSTOM_RAW,
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
public enum PrimitiveType {
|
||||
BOOLEAN("Z", "boolean"),
|
||||
CHAR("C", "char"),
|
||||
BYTE("B", "byte"),
|
||||
SHORT("S", "short"),
|
||||
INT("I", "int"),
|
||||
FLOAT("F", "float"),
|
||||
LONG("J", "long"),
|
||||
DOUBLE("D", "double"),
|
||||
OBJECT("L", "OBJECT"),
|
||||
ARRAY("[", "ARRAY"),
|
||||
VOID("V", "void");
|
||||
BOOLEAN("Z", "boolean", ArgType.object("java.lang.Boolean")),
|
||||
CHAR("C", "char", ArgType.object("java.lang.Character")),
|
||||
BYTE("B", "byte", ArgType.object("java.lang.Byte")),
|
||||
SHORT("S", "short", ArgType.object("java.lang.Short")),
|
||||
INT("I", "int", ArgType.object("java.lang.Integer")),
|
||||
FLOAT("F", "float", ArgType.object("java.lang.Float")),
|
||||
LONG("J", "long", ArgType.object("java.lang.Long")),
|
||||
DOUBLE("D", "double", ArgType.object("java.lang.Double")),
|
||||
OBJECT("L", "OBJECT", ArgType.OBJECT),
|
||||
ARRAY("[", "ARRAY", ArgType.OBJECT_ARRAY),
|
||||
VOID("V", "void", ArgType.object("java.lang.Void"));
|
||||
|
||||
private final String shortName;
|
||||
private final String longName;
|
||||
private final ArgType boxType;
|
||||
|
||||
PrimitiveType(String shortName, String longName) {
|
||||
PrimitiveType(String shortName, String longName, ArgType boxType) {
|
||||
this.shortName = shortName;
|
||||
this.longName = longName;
|
||||
this.boxType = boxType;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
@@ -29,6 +31,10 @@ public enum PrimitiveType {
|
||||
return longName;
|
||||
}
|
||||
|
||||
public ArgType getBoxType() {
|
||||
return boxType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return longName;
|
||||
|
||||
+7
-19
@@ -8,6 +8,7 @@ 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.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -34,7 +35,11 @@ public class CustomLambdaCall {
|
||||
if (values.size() < 6) {
|
||||
return false;
|
||||
}
|
||||
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
|
||||
EncodedValue mthRef = values.get(0);
|
||||
if (mthRef.getType() != EncodedType.ENCODED_METHOD_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
IMethodHandle methodHandle = (IMethodHandle) mthRef.getValue();
|
||||
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
||||
return false;
|
||||
}
|
||||
@@ -113,7 +118,7 @@ public class CustomLambdaCall {
|
||||
@NotNull
|
||||
private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode,
|
||||
MethodInfo callMthInfo) {
|
||||
InvokeType invokeType = convertInvokeType(methodHandleType);
|
||||
InvokeType invokeType = InvokeCustomUtils.convertInvokeType(methodHandleType);
|
||||
int callArgsCount = callMthInfo.getArgsCount();
|
||||
boolean instanceCall = invokeType != InvokeType.STATIC;
|
||||
if (instanceCall) {
|
||||
@@ -147,21 +152,4 @@ public class CustomLambdaCall {
|
||||
}
|
||||
return invokeNode;
|
||||
}
|
||||
|
||||
private static InvokeType convertInvokeType(MethodHandleType type) {
|
||||
switch (type) {
|
||||
case INVOKE_STATIC:
|
||||
return InvokeType.STATIC;
|
||||
case INVOKE_INSTANCE:
|
||||
return InvokeType.VIRTUAL;
|
||||
case INVOKE_DIRECT:
|
||||
case INVOKE_CONSTRUCTOR:
|
||||
return InvokeType.DIRECT;
|
||||
case INVOKE_INTERFACE:
|
||||
return InvokeType.INTERFACE;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package jadx.core.dex.instructions.invokedynamic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodHandle;
|
||||
import jadx.api.plugins.input.data.IMethodProto;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.InvokeCustomRawNode;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.EncodedValueUtils.buildLookupArg;
|
||||
import static jadx.core.utils.EncodedValueUtils.convertToInsnArg;
|
||||
|
||||
/**
|
||||
* Show `invoke-custom` similar to polymorphic call
|
||||
*/
|
||||
public class CustomRawCall {
|
||||
|
||||
public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> values) {
|
||||
IMethodHandle resolveHandle = (IMethodHandle) values.get(0).getValue();
|
||||
String invokeName = (String) values.get(1).getValue();
|
||||
IMethodProto invokeProto = (IMethodProto) values.get(2).getValue();
|
||||
List<InsnArg> resolveArgs = buildArgs(mth, values);
|
||||
|
||||
if (resolveHandle.getType().isField()) {
|
||||
throw new JadxRuntimeException("Field handle not yet supported");
|
||||
}
|
||||
|
||||
RootNode root = mth.root();
|
||||
MethodInfo resolveMth = MethodInfo.fromRef(root, resolveHandle.getMethodRef());
|
||||
InvokeType resolveInvokeType = InvokeCustomUtils.convertInvokeType(resolveHandle.getType());
|
||||
InvokeNode resolve = new InvokeNode(resolveMth, resolveInvokeType, resolveArgs.size());
|
||||
resolveArgs.forEach(resolve::addArg);
|
||||
|
||||
ClassInfo invokeCls = ClassInfo.fromType(root, ArgType.OBJECT); // type will be known at runtime
|
||||
MethodInfo invokeMth = MethodInfo.fromMethodProto(root, invokeCls, invokeName, invokeProto);
|
||||
InvokeCustomRawNode customRawNode = new InvokeCustomRawNode(resolve, invokeMth, insn, isRange);
|
||||
customRawNode.setCallSiteValues(values);
|
||||
return customRawNode;
|
||||
}
|
||||
|
||||
private static List<InsnArg> buildArgs(MethodNode mth, List<EncodedValue> values) {
|
||||
int valuesCount = values.size();
|
||||
List<InsnArg> list = new ArrayList<>(valuesCount);
|
||||
RootNode root = mth.root();
|
||||
list.add(buildLookupArg(root)); // use `java.lang.invoke.MethodHandles.lookup()` as first arg
|
||||
for (int i = 1; i < valuesCount; i++) {
|
||||
EncodedValue value = values.get(i);
|
||||
try {
|
||||
list.add(convertToInsnArg(root, value));
|
||||
} catch (Exception e) {
|
||||
mth.addWarnComment("Failed to build arg in invoke-custom insn: " + value, e);
|
||||
list.add(InsnArg.wrapArg(new ConstStringNode(value.toString())));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package jadx.core.dex.instructions.invokedynamic;
|
||||
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class InvokeCustomUtils {
|
||||
|
||||
public static InvokeType convertInvokeType(MethodHandleType type) {
|
||||
switch (type) {
|
||||
case INVOKE_STATIC:
|
||||
return InvokeType.STATIC;
|
||||
case INVOKE_INSTANCE:
|
||||
return InvokeType.VIRTUAL;
|
||||
case INVOKE_DIRECT:
|
||||
case INVOKE_CONSTRUCTOR:
|
||||
return InvokeType.DIRECT;
|
||||
case INVOKE_INTERFACE:
|
||||
return InvokeType.INTERFACE;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,33 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class EncodedValueUtils {
|
||||
|
||||
@@ -50,4 +72,108 @@ public class EncodedValueUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnArg convertToInsnArg(RootNode root, EncodedValue value) {
|
||||
Object obj = value.getValue();
|
||||
switch (value.getType()) {
|
||||
case ENCODED_NULL:
|
||||
case ENCODED_BYTE:
|
||||
case ENCODED_SHORT:
|
||||
case ENCODED_CHAR:
|
||||
case ENCODED_INT:
|
||||
case ENCODED_LONG:
|
||||
case ENCODED_FLOAT:
|
||||
case ENCODED_DOUBLE:
|
||||
return (InsnArg) convertToConstValue(value);
|
||||
|
||||
case ENCODED_BOOLEAN:
|
||||
return InsnArg.lit(((Boolean) obj) ? 0 : 1, ArgType.BOOLEAN);
|
||||
case ENCODED_STRING:
|
||||
return InsnArg.wrapArg(new ConstStringNode((String) obj));
|
||||
case ENCODED_TYPE:
|
||||
return InsnArg.wrapArg(new ConstClassNode(ArgType.parse((String) obj)));
|
||||
case ENCODED_METHOD_TYPE:
|
||||
return InsnArg.wrapArg(buildMethodType(root, (IMethodProto) obj));
|
||||
case ENCODED_METHOD_HANDLE:
|
||||
return InsnArg.wrapArg(buildMethodHandle(root, (IMethodHandle) obj));
|
||||
|
||||
}
|
||||
throw new JadxRuntimeException("Unsupported type for raw invoke-custom: " + value.getType());
|
||||
}
|
||||
|
||||
private static InvokeNode buildMethodType(RootNode root, IMethodProto methodProto) {
|
||||
ArgType retType = ArgType.parse(methodProto.getReturnType());
|
||||
List<ArgType> argTypes = Utils.collectionMap(methodProto.getArgTypes(), ArgType::parse);
|
||||
List<ArgType> callTypes = new ArrayList<>(1 + argTypes.size());
|
||||
callTypes.add(retType);
|
||||
callTypes.addAll(argTypes);
|
||||
ArgType mthType = ArgType.object("java.lang.invoke.MethodType");
|
||||
ClassInfo cls = ClassInfo.fromType(root, mthType);
|
||||
MethodInfo mth = MethodInfo.fromDetails(root, cls, "methodType", callTypes, mthType);
|
||||
InvokeNode invoke = new InvokeNode(mth, InvokeType.STATIC, callTypes.size());
|
||||
for (ArgType type : callTypes) {
|
||||
InsnNode argInsn;
|
||||
if (type.isPrimitive()) {
|
||||
argInsn = new IndexInsnNode(InsnType.SGET, getTypeField(root, type.getPrimitiveType()), 0);
|
||||
} else {
|
||||
argInsn = new ConstClassNode(type);
|
||||
}
|
||||
invoke.addArg(InsnArg.wrapArg(argInsn));
|
||||
}
|
||||
return invoke;
|
||||
}
|
||||
|
||||
public static FieldInfo getTypeField(RootNode root, PrimitiveType type) {
|
||||
ArgType boxType = type.getBoxType();
|
||||
ClassInfo boxCls = ClassInfo.fromType(root, boxType);
|
||||
return FieldInfo.from(root, boxCls, "TYPE", boxType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `MethodHandles.lookup().find{type}(methodCls, methodName, methodType)`
|
||||
*/
|
||||
private static InsnNode buildMethodHandle(RootNode root, IMethodHandle methodHandle) {
|
||||
if (methodHandle.getType().isField()) {
|
||||
// TODO: lookup for field
|
||||
return new ConstStringNode("FIELD:" + methodHandle.getFieldRef());
|
||||
}
|
||||
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||
methodRef.load();
|
||||
|
||||
ClassInfo lookupCls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles.Lookup");
|
||||
MethodInfo findMethod = MethodInfo.fromDetails(root, lookupCls,
|
||||
getFindMethodName(methodHandle.getType()),
|
||||
Arrays.asList(ArgType.CLASS, ArgType.STRING, ArgType.object("java.lang.invoke.MethodType")),
|
||||
ArgType.object("java.lang.invoke.MethodHandle"));
|
||||
|
||||
InvokeNode invoke = new InvokeNode(findMethod, InvokeType.DIRECT, 4);
|
||||
invoke.addArg(buildLookupArg(root));
|
||||
invoke.addArg(InsnArg.wrapArg(new ConstClassNode(ArgType.object(methodRef.getParentClassType()))));
|
||||
invoke.addArg(InsnArg.wrapArg(new ConstStringNode(methodRef.getName())));
|
||||
invoke.addArg(InsnArg.wrapArg(buildMethodType(root, methodRef)));
|
||||
return invoke;
|
||||
}
|
||||
|
||||
public static InsnArg buildLookupArg(RootNode root) {
|
||||
ArgType lookupType = ArgType.object("java.lang.invoke.MethodHandles.Lookup");
|
||||
ClassInfo cls = ClassInfo.fromName(root, "java.lang.invoke.MethodHandles");
|
||||
MethodInfo mth = MethodInfo.fromDetails(root, cls, "lookup", Collections.emptyList(), lookupType);
|
||||
return InsnArg.wrapArg(new InvokeNode(mth, InvokeType.STATIC, 0));
|
||||
}
|
||||
|
||||
private static String getFindMethodName(MethodHandleType type) {
|
||||
switch (type) {
|
||||
case INVOKE_STATIC:
|
||||
return "findStatic";
|
||||
case INVOKE_CONSTRUCTOR:
|
||||
return "findConstructor";
|
||||
case INVOKE_INSTANCE:
|
||||
case INVOKE_DIRECT:
|
||||
case INVOKE_INTERFACE:
|
||||
return "findVirtual";
|
||||
|
||||
default:
|
||||
return "<" + type + '>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,12 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
private @Nullable TestCompiler sourceCompiler;
|
||||
private @Nullable TestCompiler decompiledCompiler;
|
||||
|
||||
/**
|
||||
* Run check method on decompiled code even if source check method not found.
|
||||
* Useful for smali test if check method added to smali code
|
||||
*/
|
||||
private boolean forceDecompiledCheck = false;
|
||||
|
||||
static {
|
||||
// enable debug checks
|
||||
DebugChecks.checksEnabled = true;
|
||||
@@ -347,11 +353,10 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
String clsName = cls.getClassInfo().getRawName().replace('/', '.');
|
||||
try {
|
||||
// run 'check' method from original class
|
||||
if (runSourceAutoCheck(clsName)) {
|
||||
return;
|
||||
}
|
||||
boolean sourceCheckFound = runSourceAutoCheck(clsName);
|
||||
|
||||
// run 'check' method from decompiled class
|
||||
if (compile) {
|
||||
if (compile && (sourceCheckFound || forceDecompiledCheck)) {
|
||||
runDecompiledAutoCheck(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -362,36 +367,36 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
private boolean runSourceAutoCheck(String clsName) {
|
||||
if (sourceCompiler == null) {
|
||||
// no source code (smali case)
|
||||
return true;
|
||||
System.out.println("Source check: no code");
|
||||
return false;
|
||||
}
|
||||
Class<?> origCls;
|
||||
try {
|
||||
origCls = sourceCompiler.getClass(clsName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
rethrow("Missing class: " + clsName, e);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
Method checkMth;
|
||||
try {
|
||||
checkMth = sourceCompiler.getMethod(origCls, CHECK_METHOD_NAME, new Class[] {});
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (!checkMth.getReturnType().equals(void.class)
|
||||
|| !Modifier.isPublic(checkMth.getModifiers())
|
||||
|| Modifier.isStatic(checkMth.getModifiers())) {
|
||||
fail("Wrong 'check' method");
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
limitExecTime(() -> checkMth.invoke(origCls.getConstructor().newInstance()));
|
||||
System.out.println("Source check: PASSED");
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new JadxRuntimeException("Source check failed", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void runDecompiledAutoCheck(ClassNode cls) {
|
||||
@@ -554,6 +559,10 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
this.compile = false;
|
||||
}
|
||||
|
||||
protected void forceDecompiledCheck() {
|
||||
this.forceDecompiledCheck = true;
|
||||
}
|
||||
|
||||
protected void enableDeobfuscation() {
|
||||
args.setDeobfuscationOn(true);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package jadx.tests.integration.invoke;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.ConstantCallSite;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class TestRawCustomInvoke extends SmaliTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public static String func(int a, double b) {
|
||||
return String.valueOf(a + b);
|
||||
}
|
||||
|
||||
private static CallSite staticBootstrap(MethodHandles.Lookup lookup, String name, MethodType type) {
|
||||
try {
|
||||
return new ConstantCallSite(lookup.findStatic(lookup.lookupClass(), name, type));
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String test() {
|
||||
try {
|
||||
return (String) staticBootstrap(MethodHandles.lookup(), "func",
|
||||
MethodType.methodType(String.class, Integer.TYPE, Double.TYPE))
|
||||
.dynamicInvoker().invoke(1, 2.0d);
|
||||
} catch (Throwable e) {
|
||||
fail(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(test()).isEqualTo("3.0");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
// this code does not contain `invoke-custom` instruction
|
||||
// only check if equivalent polymorphic call is correct
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne(
|
||||
"return (String) staticBootstrap(MethodHandles.lookup(), \"func\", MethodType.methodType(String.class, Integer.TYPE, Double.TYPE)).dynamicInvoker().invoke(1, 2.0d);");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmali() {
|
||||
forceDecompiledCheck();
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne(
|
||||
"return (String) staticBootstrap(MethodHandles.lookup(), \"func\", MethodType.methodType(String.class, Integer.TYPE, Double.TYPE)).dynamicInvoker().invoke(1, 2.0d) /* invoke-custom */;");
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,5 @@ public class TestLambdaExtVar extends IntegrationTest {
|
||||
.code()
|
||||
.doesNotContain("lambda$")
|
||||
.containsOne("return s.equals(str);"); // TODO: simplify to expression
|
||||
|
||||
System.out.println(cls.getCode().getCodeMetadata());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
.class public Linvoke/TestRawCustomInvoke;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public static func(ID)Ljava/lang/String;
|
||||
.registers 5
|
||||
int-to-double v0, p0
|
||||
add-double/2addr v0, p1
|
||||
invoke-static {v0, v1}, Ljava/lang/String;->valueOf(D)Ljava/lang/String;
|
||||
move-result-object p0
|
||||
return-object p0
|
||||
.end method
|
||||
|
||||
.method private static staticBootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
|
||||
.registers 5
|
||||
:try_start_0
|
||||
new-instance v0, Ljava/lang/invoke/ConstantCallSite;
|
||||
invoke-virtual {p0}, Ljava/lang/invoke/MethodHandles$Lookup;->lookupClass()Ljava/lang/Class;
|
||||
move-result-object v1
|
||||
invoke-virtual {p0, v1, p1, p2}, Ljava/lang/invoke/MethodHandles$Lookup;->findStatic(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
|
||||
move-result-object p0
|
||||
invoke-direct {v0, p0}, Ljava/lang/invoke/ConstantCallSite;-><init>(Ljava/lang/invoke/MethodHandle;)V
|
||||
:try_end_d
|
||||
.catch Ljava/lang/NoSuchMethodException; {:try_start_0 .. :try_end_d} :catch_e
|
||||
.catch Ljava/lang/IllegalAccessException; {:try_start_0 .. :try_end_d} :catch_e
|
||||
return-object v0
|
||||
:catch_e
|
||||
move-exception p0
|
||||
new-instance p1, Ljava/lang/RuntimeException;
|
||||
invoke-direct {p1, p0}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/Throwable;)V
|
||||
throw p1
|
||||
.end method
|
||||
|
||||
.method public test()Ljava/lang/String;
|
||||
.registers 3
|
||||
:try_start_0
|
||||
|
||||
const/4 v0, 0x1
|
||||
const-wide/high16 v1, 0x4000000000000000L # 2.0
|
||||
invoke-custom {v0, v1}, call_site_0("func", (ID)Ljava/lang/String;)@Linvoke/TestRawCustomInvoke;->staticBootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
:try_end_25
|
||||
.catchall {:try_start_0 .. :try_end_25} :catchall_26
|
||||
return-object v0
|
||||
:catchall_26
|
||||
move-exception v0
|
||||
invoke-static {v0}, Lorg/junit/jupiter/api/Assertions;->fail(Ljava/lang/Throwable;)Ljava/lang/Object;
|
||||
const/4 v0, 0x0
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public check()V
|
||||
.registers 3
|
||||
invoke-virtual {p0}, Linvoke/TestRawCustomInvoke;->test()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
invoke-static {v0}, Ljadx/tests/api/utils/assertj/JadxAssertions;->assertThat(Ljava/lang/String;)Ljadx/tests/api/utils/assertj/JadxCodeAssertions;
|
||||
move-result-object v0
|
||||
const-string v1, "3.0"
|
||||
invoke-virtual {v0, v1}, Ljadx/tests/api/utils/assertj/JadxCodeAssertions;->isEqualTo(Ljava/lang/String;)Lorg/assertj/core/api/AbstractStringAssert;
|
||||
return-void
|
||||
.end method
|
||||
+3
-3
@@ -53,12 +53,12 @@ public class EncodedValue extends PinnedAttribute {
|
||||
switch (type) {
|
||||
case ENCODED_NULL:
|
||||
return "null";
|
||||
case ENCODED_STRING:
|
||||
return (String) value;
|
||||
case ENCODED_ARRAY:
|
||||
return "[" + value + "]";
|
||||
case ENCODED_STRING:
|
||||
return "{STRING: \"" + value + "\"}";
|
||||
default:
|
||||
return "{" + type + ": " + value + '}';
|
||||
return "{" + type.toString().substring(8) + ": " + value + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user