未验证 提交 85c2c63a 编写于 作者: S Skylot

fix: output unknown `invoke-custom` as polymorphic call (#1760)

上级 f354f7de
......@@ -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);
}
......
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,
}
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;
......
......@@ -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);
}
}
}
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;
}
}
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);
}
}
}
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);
......
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());
}
}
.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
......@@ -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 + '}';
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册