feat: concat constant strings (#1014)

This commit is contained in:
Skylot
2020-11-16 16:15:00 +00:00
parent 29ff86b74f
commit 42a44f210d
6 changed files with 164 additions and 4 deletions
@@ -1,5 +1,6 @@
package jadx.core.codegen;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,6 +98,43 @@ public class TypeGen {
}
}
@Nullable
public static String literalToRawString(LiteralArg arg) {
ArgType type = arg.getType();
if (type == null) {
return null;
}
long lit = arg.getLiteral();
switch (type.getPrimitiveType()) {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return String.valueOf((char) lit);
case BYTE:
case SHORT:
case INT:
case LONG:
return Long.toString(lit);
case FLOAT:
return Float.toString(Float.intBitsToFloat((int) lit));
case DOUBLE:
return Double.toString(Double.longBitsToDouble(lit));
case OBJECT:
case ARRAY:
if (lit != 0) {
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
return Long.toString(lit);
}
return "null";
default:
return null;
}
}
public static String formatShort(long l, boolean cast) {
if (l == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
@@ -232,6 +232,10 @@ public abstract class InsnArg extends Typed {
return contains(AFlag.THIS);
}
public boolean isConst() {
return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn());
}
protected final <T extends InsnArg> T copyCommonParams(T copy) {
copy.copyAttributesFrom(this);
copy.setParentInsn(parentInsn);
@@ -4,10 +4,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
@@ -386,7 +388,7 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
if (!stringArgFound) {
// TODO: convert one arg to string using `String.valueOf()`
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + args);
return null;
}
@@ -394,7 +396,8 @@ public class SimplifyVisitor extends AbstractVisitor {
removeStringBuilderInsns(mth, toStrInsn, chain);
List<InsnArg> dupArgs = Utils.collectionMap(args, InsnArg::duplicate);
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, dupArgs);
List<InsnArg> simplifiedArgs = concatConstArgs(dupArgs);
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, simplifiedArgs);
concatInsn.setResult(toStrInsn.getResult());
concatInsn.add(AFlag.SYNTHETIC);
concatInsn.copyAttributesFrom(toStrInsn);
@@ -408,6 +411,67 @@ public class SimplifyVisitor extends AbstractVisitor {
return null;
}
private static boolean isConstConcatNeeded(List<InsnArg> args) {
boolean prevConst = false;
for (InsnArg arg : args) {
boolean curConst = arg.isConst();
if (curConst && prevConst) {
// found 2 consecutive constants
return true;
}
prevConst = curConst;
}
return false;
}
private static List<InsnArg> concatConstArgs(List<InsnArg> args) {
if (!isConstConcatNeeded(args)) {
return args;
}
int size = args.size();
List<InsnArg> newArgs = new ArrayList<>(size);
List<String> concatList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
InsnArg arg = args.get(i);
String constStr = getConstString(arg);
if (constStr != null) {
concatList.add(constStr);
} else {
if (!concatList.isEmpty()) {
newArgs.add(getConcatArg(concatList, args, i));
concatList.clear();
}
newArgs.add(arg);
}
}
if (!concatList.isEmpty()) {
newArgs.add(getConcatArg(concatList, args, size));
}
return newArgs;
}
private static InsnArg getConcatArg(List<String> concatList, List<InsnArg> args, int idx) {
if (concatList.size() == 1) {
return args.get(idx - 1);
}
String str = Utils.concatStrings(concatList);
return InsnArg.wrapArg(new ConstStringNode(str));
}
@Nullable
private static String getConstString(InsnArg arg) {
if (arg.isLiteral()) {
return TypeGen.literalToRawString((LiteralArg) arg);
}
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (wrapInsn instanceof ConstStringNode) {
return ((ConstStringNode) wrapInsn).getString();
}
}
return null;
}
/* String concat without assign to variable will cause compilation error */
private static void checkResult(MethodNode mth, InsnNode concatInsn) {
if (concatInsn.getResult() == null) {
@@ -101,6 +101,18 @@ public class Utils {
return sb.toString();
}
public static String concatStrings(List<String> list) {
if (isEmpty(list)) {
return "";
}
if (list.size() == 1) {
return list.get(0);
}
StringBuilder sb = new StringBuilder();
list.forEach(sb::append);
return sb.toString();
}
public static String getStackTrace(Throwable throwable) {
if (throwable == null) {
return "";
@@ -0,0 +1,41 @@
package jadx.tests.integration.others;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestConstStringConcat extends IntegrationTest {
@SuppressWarnings("StringBufferReplaceableByString")
public static class TestCls {
public String test1(int value) {
return new StringBuilder().append("Value").append(" equals ").append(value).toString();
}
public String test2() {
return new StringBuilder().append("App ").append("version: ").append(1).append('.').append(2).toString();
}
public String test3(String name, int value) {
return "value " + name + " = " + value;
}
public void check() {
assertThat(test1(7)).isEqualTo("Value equals 7");
assertThat(test2()).isEqualTo("App version: 1.2");
assertThat(test3("v", 4)).isEqualTo("value v = 4");
}
}
@Test
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("return \"Value equals \" + ")
.containsOne("return \"App version: 1.2\";")
.containsOne("return \"value \" + str + \" = \" + i;");
}
}
@@ -14,6 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
*
* @author Jan Peter Stotz
*/
@SuppressWarnings("StringBufferReplaceableByString")
public class TestStringBuilderElimination2 extends IntegrationTest {
public static class TestCls1 {
@@ -27,7 +28,7 @@ public class TestStringBuilderElimination2 extends IntegrationTest {
public void test1() {
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls1.class);
String code = cls.getCode().toString();
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 2 + 0L + 1.0f + 2.0d + true;"));
assertThat(code, containsString("return \"[init]a1c201.02.0true\";"));
}
public static class TestCls2 {
@@ -49,7 +50,7 @@ public class TestStringBuilderElimination2 extends IntegrationTest {
public void test2() {
ClassNode cls = getClassNode(TestStringBuilderElimination2.TestCls2.class);
String code = cls.getCode().toString();
assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 1 + 2L + 1.0f + 2.0d + true;"));
assertThat(code, containsString("return \"[init]a1c121.02.0true\";"));
}
public static class TestClsStringUtilsReverse {