diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java index 9706f8648..2bd1c5668 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InsnDecoder.java @@ -26,6 +26,8 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.DecodeException; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.input.InsnDataUtils; public class InsnDecoder { private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class); @@ -567,12 +569,9 @@ public class InsnDecoder { if (type == InvokeType.CUSTOM) { return InvokeCustomBuilder.build(method, insn, isRange); } - IMethodRef mthRef; - ICustomPayload payload = insn.getPayload(); - if (payload != null) { - mthRef = ((IMethodRef) payload); - } else { - mthRef = insn.getIndexAsMethod(); + IMethodRef mthRef = InsnDataUtils.getMethodRef(insn); + if (mthRef == null) { + throw new JadxRuntimeException("Failed to load method reference for insn: " + insn); } MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef); return new InvokeNode(mthInfo, insn, type, isRange); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java index bc28f80e5..216d72aae 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeCustomBuilder.java @@ -2,184 +2,36 @@ package jadx.core.dex.instructions; import java.util.List; -import org.jetbrains.annotations.NotNull; - 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.api.plugins.input.insns.custom.ICustomPayload; -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.instructions.mods.ConstructorInsn; +import jadx.core.dex.instructions.invokedynamic.CustomLambdaCall; +import jadx.core.dex.instructions.invokedynamic.CustomStringConcat; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.input.InsnDataUtils; public class InvokeCustomBuilder { public static InsnNode build(MethodNode mth, InsnData insn, boolean isRange) { try { - ICallSite callSite; - ICustomPayload payload = insn.getPayload(); - if (payload != null) { - callSite = (ICallSite) payload; - } else { - callSite = insn.getIndexAsCallSite(); + ICallSite callSite = InsnDataUtils.getCallSite(insn); + if (callSite == null) { + throw new JadxRuntimeException("Failed to get call site for insn: " + insn); } callSite.load(); List values = callSite.getValues(); - if (!checkLinkerMethod(values)) { - throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite); + if (CustomLambdaCall.isLambdaInvoke(values)) { + return CustomLambdaCall.buildLambdaMethodCall(mth, insn, isRange, values); } - IMethodHandle callMthHandle = (IMethodHandle) values.get(4).getValue(); - if (callMthHandle.getType().isField()) { - throw new JadxRuntimeException("Not yet supported"); + if (CustomStringConcat.isStringConcat(values)) { + return CustomStringConcat.buildStringConcat(insn, isRange, values); } - InvokeCustomNode resNode = buildMethodCall(mth, insn, isRange, values, callMthHandle); - int resReg = insn.getResultReg(); - if (resReg != -1) { - resNode.setResult(InsnArg.reg(resReg, mth.getReturnType())); - } - return resNode; + // TODO: output raw dynamic call + throw new JadxRuntimeException("Failed to process invoke-custom instruction: " + callSite); } catch (Exception e) { throw new JadxRuntimeException("'invoke-custom' instruction processing error: " + e.getMessage(), e); } } - - @NotNull - private static InvokeCustomNode buildMethodCall(MethodNode mth, InsnData insn, boolean isRange, - List values, IMethodHandle callMthHandle) { - RootNode root = mth.root(); - IMethodProto lambdaProto = (IMethodProto) values.get(2).getValue(); - MethodInfo lambdaInfo = MethodInfo.fromMethodProto(root, mth.getParentClass().getClassInfo(), "", lambdaProto); - - MethodHandleType methodHandleType = callMthHandle.getType(); - 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(); - MethodInfo implMthInfo = MethodInfo.fromMethodProto(root, implCls, implName, implProto); - invokeCustomNode.setImplMthInfo(implMthInfo); - - MethodInfo callMthInfo = MethodInfo.fromRef(root, callMthHandle.getMethodRef()); - InvokeNode invokeNode = buildInvokeNode(methodHandleType, invokeCustomNode, callMthInfo); - - if (methodHandleType == MethodHandleType.INVOKE_CONSTRUCTOR) { - ConstructorInsn ctrInsn = new ConstructorInsn(mth, invokeNode); - invokeCustomNode.setCallInsn(ctrInsn); - } else { - invokeCustomNode.setCallInsn(invokeNode); - } - - MethodNode callMth = root.resolveMethod(callMthInfo); - if (callMth != null) { - invokeCustomNode.getCallInsn().addAttr(callMth); - if (callMth.getAccessFlags().isSynthetic() - && callMth.getParentClass().equals(mth.getParentClass())) { - // inline only synthetic methods from same class - callMth.add(AFlag.DONT_GENERATE); - invokeCustomNode.setInlineInsn(true); - } - } - if (!invokeCustomNode.isInlineInsn()) { - IMethodProto effectiveMthProto = (IMethodProto) values.get(5).getValue(); - List args = Utils.collectionMap(effectiveMthProto.getArgTypes(), ArgType::parse); - boolean sameArgs = args.equals(callMthInfo.getArgumentsTypes()); - invokeCustomNode.setUseRef(sameArgs); - } - - // prevent args inlining into not generated invoke custom node - for (InsnArg arg : invokeCustomNode.getArguments()) { - arg.add(AFlag.DONT_INLINE); - } - return invokeCustomNode; - } - - @NotNull - private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode, - MethodInfo callMthInfo) { - InvokeType invokeType = convertInvokeType(methodHandleType); - int callArgsCount = callMthInfo.getArgsCount(); - boolean instanceCall = invokeType != InvokeType.STATIC; - if (instanceCall) { - callArgsCount++; - } - InvokeNode invokeNode = new InvokeNode(callMthInfo, invokeType, callArgsCount); - - // copy insn args - int argsCount = invokeCustomNode.getArgsCount(); - for (int i = 0; i < argsCount; i++) { - InsnArg arg = invokeCustomNode.getArg(i); - invokeNode.addArg(arg.duplicate()); - } - if (callArgsCount > argsCount) { - // fill remaining args with NamedArg - int callArgNum = argsCount; - if (instanceCall) { - callArgNum--; // start from instance type - } - List callArgTypes = callMthInfo.getArgumentsTypes(); - for (int i = argsCount; i < callArgsCount; i++) { - ArgType argType; - if (callArgNum < 0) { - // instance arg type - argType = callMthInfo.getDeclClass().getType(); - } else { - argType = callArgTypes.get(callArgNum++); - } - invokeNode.addArg(new NamedArg("v" + i, argType)); - } - } - return invokeNode; - } - - /** - * Expect LambdaMetafactory.metafactory method - */ - private static boolean checkLinkerMethod(List 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: - case INVOKE_CONSTRUCTOR: - return InvokeType.DIRECT; - case INVOKE_INTERFACE: - return InvokeType.INTERFACE; - - default: - throw new JadxRuntimeException("Unsupported method handle type: " + type); - } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomLambdaCall.java b/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomLambdaCall.java new file mode 100644 index 000000000..090c7ef7a --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomLambdaCall.java @@ -0,0 +1,169 @@ +package jadx.core.dex.instructions.invokedynamic; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +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.InvokeCustomNode; +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.NamedArg; +import jadx.core.dex.instructions.mods.ConstructorInsn; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class CustomLambdaCall { + + /** + * Expect LambdaMetafactory.metafactory method + */ + public static boolean isLambdaInvoke(List 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; + } + + public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List values) { + IMethodHandle callMthHandle = (IMethodHandle) values.get(4).getValue(); + if (callMthHandle.getType().isField()) { + throw new JadxRuntimeException("Not yet supported"); + } + InvokeCustomNode resNode = buildMethodCall(mth, insn, isRange, values, callMthHandle); + int resReg = insn.getResultReg(); + if (resReg != -1) { + resNode.setResult(InsnArg.reg(resReg, mth.getReturnType())); + } + return resNode; + } + + @NotNull + private static InvokeCustomNode buildMethodCall(MethodNode mth, InsnData insn, boolean isRange, + List values, IMethodHandle callMthHandle) { + RootNode root = mth.root(); + IMethodProto lambdaProto = (IMethodProto) values.get(2).getValue(); + MethodInfo lambdaInfo = MethodInfo.fromMethodProto(root, mth.getParentClass().getClassInfo(), "", lambdaProto); + + MethodHandleType methodHandleType = callMthHandle.getType(); + 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(); + MethodInfo implMthInfo = MethodInfo.fromMethodProto(root, implCls, implName, implProto); + invokeCustomNode.setImplMthInfo(implMthInfo); + + MethodInfo callMthInfo = MethodInfo.fromRef(root, callMthHandle.getMethodRef()); + InvokeNode invokeNode = buildInvokeNode(methodHandleType, invokeCustomNode, callMthInfo); + + if (methodHandleType == MethodHandleType.INVOKE_CONSTRUCTOR) { + ConstructorInsn ctrInsn = new ConstructorInsn(mth, invokeNode); + invokeCustomNode.setCallInsn(ctrInsn); + } else { + invokeCustomNode.setCallInsn(invokeNode); + } + + MethodNode callMth = root.resolveMethod(callMthInfo); + if (callMth != null) { + invokeCustomNode.getCallInsn().addAttr(callMth); + if (callMth.getAccessFlags().isSynthetic() + && callMth.getParentClass().equals(mth.getParentClass())) { + // inline only synthetic methods from same class + callMth.add(AFlag.DONT_GENERATE); + invokeCustomNode.setInlineInsn(true); + } + } + if (!invokeCustomNode.isInlineInsn()) { + IMethodProto effectiveMthProto = (IMethodProto) values.get(5).getValue(); + List args = Utils.collectionMap(effectiveMthProto.getArgTypes(), ArgType::parse); + boolean sameArgs = args.equals(callMthInfo.getArgumentsTypes()); + invokeCustomNode.setUseRef(sameArgs); + } + + // prevent args inlining into not generated invoke custom node + for (InsnArg arg : invokeCustomNode.getArguments()) { + arg.add(AFlag.DONT_INLINE); + } + return invokeCustomNode; + } + + @NotNull + private static InvokeNode buildInvokeNode(MethodHandleType methodHandleType, InvokeCustomNode invokeCustomNode, + MethodInfo callMthInfo) { + InvokeType invokeType = convertInvokeType(methodHandleType); + int callArgsCount = callMthInfo.getArgsCount(); + boolean instanceCall = invokeType != InvokeType.STATIC; + if (instanceCall) { + callArgsCount++; + } + InvokeNode invokeNode = new InvokeNode(callMthInfo, invokeType, callArgsCount); + + // copy insn args + int argsCount = invokeCustomNode.getArgsCount(); + for (int i = 0; i < argsCount; i++) { + InsnArg arg = invokeCustomNode.getArg(i); + invokeNode.addArg(arg.duplicate()); + } + if (callArgsCount > argsCount) { + // fill remaining args with NamedArg + int callArgNum = argsCount; + if (instanceCall) { + callArgNum--; // start from instance type + } + List callArgTypes = callMthInfo.getArgumentsTypes(); + for (int i = argsCount; i < callArgsCount; i++) { + ArgType argType; + if (callArgNum < 0) { + // instance arg type + argType = callMthInfo.getDeclClass().getType(); + } else { + argType = callArgTypes.get(callArgNum++); + } + invokeNode.addArg(new NamedArg("v" + i, argType)); + } + } + 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); + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomStringConcat.java b/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomStringConcat.java new file mode 100644 index 000000000..95b509872 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/invokedynamic/CustomStringConcat.java @@ -0,0 +1,117 @@ +package jadx.core.dex.instructions.invokedynamic; + +import java.util.List; +import java.util.Objects; + +import jadx.api.plugins.input.data.IMethodHandle; +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; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.JadxError; +import jadx.core.dex.instructions.ConstClassNode; +import jadx.core.dex.instructions.ConstStringNode; +import jadx.core.dex.instructions.InsnType; +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.nodes.InsnNode; +import jadx.core.utils.EncodedValueUtils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class CustomStringConcat { + + public static boolean isStringConcat(List values) { + if (values.size() < 4) { + 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("makeConcatWithConstants")) { + return false; + } + if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/StringConcatFactory;")) { + return false; + } + if (!Objects.equals(values.get(1).getValue(), "makeConcatWithConstants")) { + return false; + } + if (values.get(3).getType() != EncodedType.ENCODED_STRING) { + return false; + } + return true; + } + + public static InsnNode buildStringConcat(InsnData insn, boolean isRange, List values) { + try { + int argsCount = values.size() - 3 + insn.getRegsCount(); + InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount); + String recipe = (String) values.get(3).getValue(); + processRecipe(recipe, concat, values, insn); + int resReg = insn.getResultReg(); + if (resReg != -1) { + concat.setResult(InsnArg.reg(resReg, ArgType.STRING)); + } + return concat; + } catch (Exception e) { + InsnNode nop = new InsnNode(InsnType.NOP, 0); + nop.add(AFlag.SYNTHETIC); + nop.addAttr(AType.JADX_ERROR, new JadxError("Failed to process dynamic string concat: " + e.getMessage(), e)); + return nop; + } + } + + private static void processRecipe(String recipe, InsnNode concat, List values, InsnData insn) { + int len = recipe.length(); + int offset = 0; + int argNum = 0; + int constNum = 4; + StringBuilder sb = new StringBuilder(len); + while (offset < len) { + int cp = recipe.codePointAt(offset); + offset += Character.charCount(cp); + boolean argTag = cp == 1; + boolean constTag = cp == 2; + if (argTag || constTag) { + if (sb.length() != 0) { + concat.addArg(InsnArg.wrapArg(new ConstStringNode(sb.toString()))); + sb.setLength(0); + } + if (argTag) { + concat.addArg(InsnArg.reg(insn, argNum++, ArgType.UNKNOWN)); + } else { + InsnArg constArg = buildInsnArgFromEncodedValue(values.get(constNum++)); + concat.addArg(constArg); + } + } else { + sb.appendCodePoint(cp); + } + } + if (sb.length() != 0) { + concat.addArg(InsnArg.wrapArg(new ConstStringNode(sb.toString()))); + } + } + + private static InsnArg buildInsnArgFromEncodedValue(EncodedValue encodedValue) { + Object value = EncodedValueUtils.convertToConstValue(encodedValue); + if (value == null) { + return InsnArg.lit(0, ArgType.UNKNOWN); + } + if (value instanceof LiteralArg) { + return ((LiteralArg) value); + } + if (value instanceof ArgType) { + return InsnArg.wrapArg(new ConstClassNode((ArgType) value)); + } + if (value instanceof String) { + return InsnArg.wrapArg(new ConstStringNode(((String) value))); + } + throw new JadxRuntimeException("Can't build insn arg from encoded value: " + encodedValue); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index f8c454603..0b1bd81d6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -18,6 +18,7 @@ import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.OverrideMethodVisitor; import jadx.core.dex.visitors.RenameVisitor; +import jadx.core.utils.input.InsnDataUtils; @JadxVisitor( name = "UsageInfoVisitor", @@ -120,18 +121,14 @@ public class UsageInfoVisitor extends AbstractVisitor { case CALL_SITE: { insnData.decode(); - ICallSite callSite; - ICustomPayload payload = insnData.getPayload(); - if (payload != null) { - callSite = ((ICallSite) payload); - } else { - callSite = insnData.getIndexAsCallSite(); - } - IMethodHandle methodHandle = (IMethodHandle) callSite.getValues().get(4).getValue(); - IMethodRef mthRef = methodHandle.getMethodRef(); - MethodNode mthNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef)); - if (mthNode != null) { - usageInfo.methodUse(mth, mthNode); + ICallSite callSite = InsnDataUtils.getCallSite(insnData); + IMethodHandle methodHandle = InsnDataUtils.getMethodHandleAt(callSite, 4); + if (methodHandle != null) { + IMethodRef mthRef = methodHandle.getMethodRef(); + MethodNode mthNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef)); + if (mthNode != null) { + usageInfo.methodUse(mth, mthNode); + } } break; } diff --git a/jadx-core/src/main/java/jadx/core/utils/input/InsnDataUtils.java b/jadx-core/src/main/java/jadx/core/utils/input/InsnDataUtils.java new file mode 100644 index 000000000..ccc2b5afc --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/input/InsnDataUtils.java @@ -0,0 +1,56 @@ +package jadx.core.utils.input; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +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.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.custom.ICustomPayload; + +public class InsnDataUtils { + + @Nullable + public static ICallSite getCallSite(InsnData insnData) { + if (insnData.getIndexType() != InsnIndexType.CALL_SITE) { + return null; + } + ICustomPayload payload = insnData.getPayload(); + if (payload != null) { + return ((ICallSite) payload); + } + return insnData.getIndexAsCallSite(); + } + + @Nullable + public static IMethodRef getMethodRef(InsnData insnData) { + if (insnData.getIndexType() != InsnIndexType.METHOD_REF) { + return null; + } + ICustomPayload payload = insnData.getPayload(); + if (payload != null) { + return ((IMethodRef) payload); + } + return insnData.getIndexAsMethod(); + } + + @Nullable + public static IMethodHandle getMethodHandleAt(ICallSite callSite, int argNum) { + if (callSite == null) { + return null; + } + List values = callSite.getValues(); + if (argNum < values.size()) { + EncodedValue encodedValue = values.get(argNum); + if (encodedValue.getType() == EncodedType.ENCODED_METHOD_HANDLE) { + return (IMethodHandle) encodedValue.getValue(); + } + } + return null; + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 5303688df..8c2de54d2 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -224,7 +224,7 @@ public abstract class IntegrationTest extends TestUtils { } System.out.println("-----------------------------------------------------------"); if (printDisassemble) { - clsList.forEach(this::printSmali); + clsList.forEach(this::printDisasm); } runChecks(clsList); } @@ -239,7 +239,7 @@ public abstract class IntegrationTest extends TestUtils { clsList.forEach(this::runAutoCheck); } - private void printSmali(ClassNode cls) { + private void printDisasm(ClassNode cls) { System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); System.out.println(cls.getDisassembledCode()); System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatJava11.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatJava11.java new file mode 100644 index 000000000..aa68b238f --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatJava11.java @@ -0,0 +1,52 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.RaungTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestStringConcatJava11 extends RaungTest { + + public static class TestCls { + public String test(final String s) { + return s + "test"; + } + + //@formatter:off + /* Dynamic call looks like this: + public String test(final String s) { + return java.lang.invoke.StringConcatFactory.makeConcatWithConstants( + java.lang.invoke.MethodHandles.lookup(), + "makeConcatWithConstants", + java.lang.invoke.MethodType.fromMethodDescriptorString("(Ljava/lang/String;)Ljava/lang/String;", this.getClass().getClassLoader()), + "\u0001test" + ).dynamicInvoker().invoke(s); + } + */ + //@formatter:on + + public String test2(final String s) { + return s + "test" + s + 7; + } + } + + @Test + public void test() { + assertThat(getClassNodeFromRaung()) + .code() + .containsOne("return str + \"test\";") + .containsOne("return str + \"test\" + str + 7;"); + } + + @Test + public void testJava() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return str + \"test\";") + .containsOneOf( + "return str + \"test\" + str + 7;", + "return str + \"test\" + str + \"7\";"); // dynamic concat add const to string recipe + } +} diff --git a/jadx-core/src/test/raung/others/TestStringConcatJava11.raung b/jadx-core/src/test/raung/others/TestStringConcatJava11.raung new file mode 100644 index 000000000..81870304e --- /dev/null +++ b/jadx-core/src/test/raung/others/TestStringConcatJava11.raung @@ -0,0 +1,28 @@ +.version 55 +.class public others/TestStringConcatJava11 + +.method public test(Ljava/lang/String;)Ljava/lang/String; + .max stack 1 + .max locals 2 + + aload 1 + invokedynamic makeConcatWithConstants (Ljava/lang/String;)Ljava/lang/String; + .handle invoke-static java/lang/invoke/StringConcatFactory makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; + .arg 0 "\u0001test" + .end invokedynamic + areturn +.end method + +.method public test2(Ljava/lang/String;)Ljava/lang/String; + .max stack 2 + .max locals 2 + + aload 1 + aload 1 + invokedynamic makeConcatWithConstants (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + .handle invoke-static java/lang/invoke/StringConcatFactory makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; + .arg 0 "\u0001test\u0001\u0002" + .arg 1 7 # synthetic usage, compiler adds const values into recipe string + .end invokedynamic + areturn +.end method diff --git a/jadx-plugins/jadx-java-input/build.gradle b/jadx-plugins/jadx-java-input/build.gradle index 5665daafb..cd943d399 100644 --- a/jadx-plugins/jadx-java-input/build.gradle +++ b/jadx-plugins/jadx-java-input/build.gradle @@ -6,6 +6,5 @@ dependencies { api(project(":jadx-plugins:jadx-plugins-api")) // show bytecode disassemble - implementation 'org.ow2.asm:asm:9.2' - implementation 'org.ow2.asm:asm-util:9.2' + implementation 'io.github.skylot:raung-disasm:0.0.2' } diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java index 5c9e0eb50..9f9a1f307 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/utils/DisasmUtils.java @@ -1,32 +1,29 @@ package jadx.plugins.input.java.utils; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; -import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.concurrent.TimeUnit; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.util.TraceClassVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.github.skylot.raung.disasm.RaungDisasm; + public class DisasmUtils { private static final Logger LOG = LoggerFactory.getLogger(DisasmUtils.class); public static String get(byte[] bytes) { - return useASM(bytes); + return useRaung(bytes); } - private static String useASM(byte[] bytes) { - StringWriter out = new StringWriter(); - TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(out)); - new ClassReader(bytes).accept(tcv, 0); - return out.toString(); + private static String useRaung(byte[] bytes) { + return RaungDisasm.create() + .executeForInputStream(new ByteArrayInputStream(bytes)); } /** diff --git a/jadx-plugins/jadx-raung-input/build.gradle b/jadx-plugins/jadx-raung-input/build.gradle index 2d585c9ff..b9a304f2b 100644 --- a/jadx-plugins/jadx-raung-input/build.gradle +++ b/jadx-plugins/jadx-raung-input/build.gradle @@ -7,5 +7,5 @@ dependencies { implementation(project(":jadx-plugins:jadx-java-input")) - implementation('io.github.skylot:raung-asm:0.0.1') + implementation('io.github.skylot:raung-asm:0.0.2') }