diff --git a/src/main/java/jadx/Consts.java b/src/main/java/jadx/Consts.java index 4dfe22530..c119e0dbf 100644 --- a/src/main/java/jadx/Consts.java +++ b/src/main/java/jadx/Consts.java @@ -13,5 +13,7 @@ public class Consts { public static final String CLASS_THROWABLE = "java.lang.Throwable"; public static final String CLASS_ENUM = "java.lang.Enum"; + public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder"; + public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature"; } diff --git a/src/main/java/jadx/codegen/InsnGen.java b/src/main/java/jadx/codegen/InsnGen.java index 077eb0669..729cf988a 100644 --- a/src/main/java/jadx/codegen/InsnGen.java +++ b/src/main/java/jadx/codegen/InsnGen.java @@ -32,6 +32,7 @@ import jadx.utils.exceptions.CodegenException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -213,7 +214,7 @@ public class InsnGen { case RETURN: if (insn.getArgsCount() != 0) - code.add("return " + arg(insn.getArg(0))); + code.add("return ").add(arg(insn.getArg(0))); else code.add("return"); break; @@ -227,7 +228,7 @@ public class InsnGen { break; case THROW: - code.add("throw " + arg(insn.getArg(0))); + code.add("throw ").add(arg(insn.getArg(0))); break; case CMP_L: @@ -281,7 +282,7 @@ public class InsnGen { code.add(ifield((IndexInsnNode) insn, 0)); break; case IPUT: - code.add(ifield((IndexInsnNode) insn, 1) + " = " + arg(insn.getArg(0))); + code.add(ifield((IndexInsnNode) insn, 1)).add(" = ").add(arg(insn.getArg(0))); break; case SGET: @@ -290,7 +291,18 @@ public class InsnGen { case SPUT: IndexInsnNode node = (IndexInsnNode) insn; fieldPut(node); - code.add(sfield(node) + " = " + arg(node.getArg(0))); + code.add(sfield(node)).add(" = ").add(arg(node.getArg(0))); + break; + + case STR_CONCAT: + // TODO: wrap in braces only if necessary + code.add('('); + for (Iterator it = insn.getArguments().iterator(); it.hasNext(); ) { + code.add(arg(it.next())); + if (it.hasNext()) + code.add(" + "); + } + code.add(')'); break; case MONITOR_ENTER: diff --git a/src/main/java/jadx/dex/info/MethodInfo.java b/src/main/java/jadx/dex/info/MethodInfo.java index 8cd78f381..ee8831b95 100644 --- a/src/main/java/jadx/dex/info/MethodInfo.java +++ b/src/main/java/jadx/dex/info/MethodInfo.java @@ -49,6 +49,10 @@ public final class MethodInfo { return declClass.getFullName() + "." + name; } + + public String getFullId() { + return declClass.getFullName() + "." + shortId; + } /** * Method name and signature */ diff --git a/src/main/java/jadx/dex/instructions/InsnType.java b/src/main/java/jadx/dex/instructions/InsnType.java index 156984e82..4684c8519 100644 --- a/src/main/java/jadx/dex/instructions/InsnType.java +++ b/src/main/java/jadx/dex/instructions/InsnType.java @@ -51,6 +51,8 @@ public enum InsnType { BREAK, CONTINUE, + STR_CONCAT, // strings concatenation + TERNARY, ARGS, // just generate arguments diff --git a/src/main/java/jadx/dex/nodes/MethodNode.java b/src/main/java/jadx/dex/nodes/MethodNode.java index c27364c80..351da71c4 100644 --- a/src/main/java/jadx/dex/nodes/MethodNode.java +++ b/src/main/java/jadx/dex/nodes/MethodNode.java @@ -65,12 +65,12 @@ public class MethodNode extends AttrNode implements ILoadable { private IContainer region; private List exceptionHandlers; - public MethodNode(ClassNode classNode, Method mth) { - this.mthInfo = MethodInfo.fromDex(classNode.dex(), mth.getMethodIndex()); + public MethodNode(ClassNode classNode, Method mthData) { + this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex()); this.parentClass = classNode; - this.accFlags = new AccessInfo(mth.getAccessFlags(), AFType.METHOD); - this.methodData = mth; - this.noCode = (methodData.getCodeOffset() == 0); + this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD); + this.noCode = (mthData.getCodeOffset() == 0); + this.methodData = (noCode ? null : mthData); } @Override diff --git a/src/main/java/jadx/dex/visitors/CodeShrinker.java b/src/main/java/jadx/dex/visitors/CodeShrinker.java index c9b62ce78..a399afb56 100644 --- a/src/main/java/jadx/dex/visitors/CodeShrinker.java +++ b/src/main/java/jadx/dex/visitors/CodeShrinker.java @@ -1,14 +1,18 @@ package jadx.dex.visitors; +import jadx.Consts; import jadx.dex.attributes.AttributeFlag; +import jadx.dex.info.MethodInfo; import jadx.dex.instructions.ArithNode; import jadx.dex.instructions.ArithOp; import jadx.dex.instructions.IfNode; import jadx.dex.instructions.InsnType; +import jadx.dex.instructions.InvokeNode; import jadx.dex.instructions.args.InsnArg; import jadx.dex.instructions.args.InsnWrapArg; import jadx.dex.instructions.args.LiteralArg; import jadx.dex.instructions.args.RegisterArg; +import jadx.dex.instructions.mods.ConstructorInsn; import jadx.dex.nodes.BlockNode; import jadx.dex.nodes.InsnNode; import jadx.dex.nodes.MethodNode; @@ -16,6 +20,7 @@ import jadx.utils.BlockUtils; import jadx.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.slf4j.Logger; @@ -154,12 +159,49 @@ public class CodeShrinker extends AbstractVisitor { } break; + case INVOKE: + MethodInfo callMth = ((InvokeNode) insn).getCallMth(); + if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER) + && callMth.getShortId().equals("toString()") + && insn.getArg(0).isInsnWrap()) { + List chain = flattenInsnChain(insn); + if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) { + ConstructorInsn constr = (ConstructorInsn) chain.get(0); + if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER) + && constr.getArgsCount() == 0) { + int len = chain.size(); + InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1); + for (int i = 1; i < len; i++) { + concatInsn.addArg(chain.get(i).getArg(1)); + } + concatInsn.setResult(insn.getResult()); + return concatInsn; + } + } + } + break; + default: break; } return null; } + private static List flattenInsnChain(InsnNode insn) { + List chain = new ArrayList(); + InsnArg i = insn.getArg(0); + while (i.isInsnWrap()) { + InsnNode wrapInsn = ((InsnWrapArg) i).getWrapInsn(); + chain.add(wrapInsn); + if(wrapInsn.getArgsCount() == 0) + break; + + i = wrapInsn.getArg(0); + } + Collections.reverse(chain); + return chain; + } + public static InsnArg inlineArgument(MethodNode mth, RegisterArg arg) { InsnNode assignInsn = arg.getAssignInsn(); if (assignInsn == null)