diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index 7e236097f6a5313aee179b99ef97662b7215e763..73d5e420298f9148034c8f2baed13dc4bd2774d6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -33,6 +33,21 @@ public final class MethodInfo { shortId = makeSignature(true); } + private MethodInfo(ClassInfo declClass, String name, List args, ArgType retType) { + this.name = name; + alias = name; + aliasFromPreset = false; + this.declClass = declClass; + + this.args = args; + this.retType = retType; + shortId = makeSignature(true); + } + + public static MethodInfo externalMth(ClassInfo declClass, String name, List args, ArgType retType) { + return new MethodInfo(declClass, name, args, retType); + } + public static MethodInfo fromDex(DexNode dex, int mthIndex) { MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex); if (mth != null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index 7342c2e75e655357b729d0f2bcefc8b38aff9810..a2dcb8ded8edb7ec94df018c8c4b642e56ce7dac 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -36,7 +36,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface { } } - private InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) { + public InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) { super(InsnType.INVOKE, argsCount); this.mth = mth; this.type = invokeType; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index 7f1196b55ff6929f6cfa887e6fc1012e48b00b5e..b8b81ab0779a68cdf106b327c2b4c42cf63f179f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -8,7 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; +import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.ArithNode; @@ -19,6 +21,7 @@ import jadx.core.dex.instructions.IfNode; 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.FieldArg; import jadx.core.dex.instructions.args.InsnArg; @@ -29,6 +32,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.dex.regions.conditions.IfCondition; public class SimplifyVisitor extends AbstractVisitor { @@ -95,12 +99,55 @@ public class SimplifyVisitor extends AbstractVisitor { } break; + case CONSTRUCTOR: + simplfyConstructor(mth.root(), (ConstructorInsn) insn); + break; + default: break; } return null; } + private static void simplfyConstructor(RootNode root, ConstructorInsn insn) { + if (insn.getArgsCount() != 0 + && insn.getCallMth().getDeclClass().getType().equals(ArgType.STRING)) { + InsnArg arg = insn.getArg(0); + InsnNode node = arg.isInsnWrap() + ? ((InsnWrapArg) arg).getWrapInsn() + : insn; + if (node.getArgsCount() != 0) { + ArgType argType = node.getArg(0).getType(); + if (node.getType() == InsnType.FILLED_NEW_ARRAY + && (argType == ArgType.BYTE || argType == ArgType.CHAR)) { + int printable = 0; + byte[] arr = new byte[node.getArgsCount()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (byte) ((LiteralArg) node.getArg(i)).getLiteral(); + if (NameMapper.isPrintableChar(arr[i])) { + printable++; + } + } + if (printable >= arr.length - printable) { + InsnWrapArg wa = new InsnWrapArg(new ConstStringNode(new String(arr))); + if (insn.getArgsCount() == 1) { + insn.setArg(0, wa); + } else { + MethodInfo mi = MethodInfo.externalMth( + ClassInfo.fromType(root, ArgType.STRING), + "getBytes", + Collections.emptyList(), + ArgType.array(ArgType.BYTE)); + InvokeNode in = new InvokeNode(mi, InvokeType.VIRTUAL, 1); + in.addArg(wa); + insn.setArg(0, new InsnWrapArg(in)); + } + } + } + } + } + } + private static InsnNode processCast(MethodNode mth, InsnNode insn) { InsnArg castArg = insn.getArg(0); ArgType argType = castArg.getType(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConstructor.java new file mode 100644 index 0000000000000000000000000000000000000000..be8fb645eb7a815ae5d0a7a87031174cacc4ea42 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConstructor.java @@ -0,0 +1,86 @@ +package jadx.tests.integration.others; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +public class TestStringConstructor extends IntegrationTest { + + public static class TestCls { + public String tag = new String(new byte[] {'a', 'b', 'c'}); + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("abc")); + } + + public static class TestCls2 { + public String tag = new String(new byte[] {'a', 'b', 'c'}, StandardCharsets.UTF_8); + } + + @Test + public void test2() { + ClassNode cls = getClassNode(TestCls2.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("new String(\"abc\".getBytes(), StandardCharsets.UTF_8)")); + } + + public static class TestCls3 { + public String tag = new String(new byte[] {1, 2, 3, 'a', 'b', 'c'}); + } + + @Test + public void test3() { + ClassNode cls = getClassNode(TestCls3.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("\\u0001\\u0002\\u0003abc")); + } + + public static class TestCls4 { + public String tag = new String(new char[] {1, 2, 3, 'a', 'b', 'c'}); + } + + @Test + public void test4() { + ClassNode cls = getClassNode(TestCls4.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("\\u0001\\u0002\\u0003abc")); + } + + public static class TestCls5 { + public String tag = new String(new char[] {1, 2, 3, 'a', 'b'}); + } + + @Test + public void test5() { + ClassNode cls = getClassNode(TestCls5.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("{1, 2, 3, 'a', 'b'}")); + } + + public static class TestClsNegative { + public String tag = new String(); + } + + @Test + public void testNegative() { + ClassNode cls = getClassNode(TestClsNegative.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("tag = new String();")); + } +}