feat: concat constant strings (#1014)
This commit is contained in:
@@ -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;");
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user