fix: support dynamic strings concat (#1250)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<EncodedValue> 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<EncodedValue> 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<ArgType> 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<ArgType> 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<EncodedValue> values) {
|
||||
if (values.size() < 6) {
|
||||
return false;
|
||||
}
|
||||
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
|
||||
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
||||
return false;
|
||||
}
|
||||
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||
if (!methodRef.getName().equals("metafactory")) {
|
||||
return false;
|
||||
}
|
||||
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static InvokeType convertInvokeType(MethodHandleType type) {
|
||||
switch (type) {
|
||||
case INVOKE_STATIC:
|
||||
return InvokeType.STATIC;
|
||||
case INVOKE_INSTANCE:
|
||||
return InvokeType.VIRTUAL;
|
||||
case INVOKE_DIRECT:
|
||||
case INVOKE_CONSTRUCTOR:
|
||||
return InvokeType.DIRECT;
|
||||
case INVOKE_INTERFACE:
|
||||
return InvokeType.INTERFACE;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported method handle type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+169
@@ -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<EncodedValue> values) {
|
||||
if (values.size() < 6) {
|
||||
return false;
|
||||
}
|
||||
IMethodHandle methodHandle = (IMethodHandle) values.get(0).getValue();
|
||||
if (methodHandle.getType() != MethodHandleType.INVOKE_STATIC) {
|
||||
return false;
|
||||
}
|
||||
IMethodRef methodRef = methodHandle.getMethodRef();
|
||||
if (!methodRef.getName().equals("metafactory")) {
|
||||
return false;
|
||||
}
|
||||
if (!methodRef.getParentClassType().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static InvokeCustomNode buildLambdaMethodCall(MethodNode mth, InsnData insn, boolean isRange, List<EncodedValue> 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<EncodedValue> 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<ArgType> 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<ArgType> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -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<EncodedValue> 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<EncodedValue> 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<EncodedValue> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<EncodedValue> 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;
|
||||
}
|
||||
}
|
||||
@@ -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("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
+7
-10
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user