diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index 908047fe0..639134070 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -4,6 +4,7 @@ public class Consts { public static final boolean DEBUG = false; public static final boolean DEBUG_USAGE = false; public static final boolean DEBUG_TYPE_INFERENCE = false; + public static final boolean DEBUG_OVERLOADED_CASTS = false; public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_STRING = "java.lang.String"; diff --git a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java index 40a67d62c..2fc6845d4 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java @@ -3,7 +3,6 @@ package jadx.core.codegen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; @@ -71,11 +70,7 @@ public class TypeGen { case BOOLEAN: return lit == 0 ? "false" : "true"; case CHAR: - char ch = (char) lit; - if (!NameMapper.isPrintableChar(ch)) { - return Integer.toString(ch); - } - return stringUtils.unescapeChar(ch); + return stringUtils.unescapeChar((char) lit, cast); case BYTE: return formatByte(lit, cast); case SHORT: diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java index e9401eedc..47f1b80da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java @@ -278,13 +278,14 @@ public class MethodInvokeVisitor extends AbstractVisitor { if (argsCount == 1) { return mthDetails.getArgTypes(); } - // TODO: try to minimize casts count - parentMth.addComment("JADX DEBUG: Failed to find minimal casts for resolve overloaded methods, cast all args instead" - + NL + " method: " + mthDetails - + NL + " arg types: " + compilerVarTypes - + NL + " candidates:" - + NL + " " + Utils.listToString(overloadedMethods, NL + " ")); - + if (Consts.DEBUG_OVERLOADED_CASTS) { + // TODO: try to minimize casts count + parentMth.addComment("JADX DEBUG: Failed to find minimal casts for resolve overloaded methods, cast all args instead" + + NL + " method: " + mthDetails + + NL + " arg types: " + compilerVarTypes + + NL + " candidates:" + + NL + " " + Utils.listToString(overloadedMethods, NL + " ")); + } // not resolved -> cast all args return mthDetails.getArgTypes(); } diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index 046f8ac0d..70dde6bbd 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -1,6 +1,9 @@ package jadx.core.utils; +import org.jetbrains.annotations.Nullable; + import jadx.api.JadxArgs; +import jadx.core.deobf.NameMapper; public class StringUtils { private static final StringUtils DEFAULT_INSTANCE = new StringUtils(new JadxArgs()); @@ -24,57 +27,74 @@ public class StringUtils { res.append('"'); for (int i = 0; i < len; i++) { int c = str.charAt(i) & 0xFFFF; - processChar(c, res); + processCharInsideString(c, res); } res.append('"'); return res.toString(); } - public String unescapeChar(char ch) { - if (ch == '\'') { - return "'\\\''"; + private void processCharInsideString(int c, StringBuilder res) { + String str = getSpecialStringForChar(c); + if (str != null) { + res.append(str); + return; + } + if (c < 32 || c >= 127 && escapeUnicode) { + res.append("\\u").append(String.format("%04x", c)); + } else { + res.append((char) c); } - StringBuilder res = new StringBuilder(); - res.append('\''); - processChar(ch, res); - res.append('\''); - return res.toString(); } - private void processChar(int c, StringBuilder res) { + /** + * Represent single char best way possible + */ + public String unescapeChar(int c, boolean explicitCast) { + if (c == '\'') { + return "'\\''"; + } + String str = getSpecialStringForChar(c); + if (str != null) { + return '\'' + str + '\''; + } + if (c >= 127 && escapeUnicode) { + return String.format("'\\u%04x'", c); + } + if (NameMapper.isPrintableChar(c)) { + return "'" + (char) c + '\''; + } + if (explicitCast) { + return "(char) " + c; + } + return String.valueOf(c); + } + + public String unescapeChar(char ch) { + return unescapeChar(ch, false); + } + + @Nullable + private String getSpecialStringForChar(int c) { switch (c) { case '\n': - res.append("\\n"); - break; + return "\\n"; case '\r': - res.append("\\r"); - break; + return "\\r"; case '\t': - res.append("\\t"); - break; + return "\\t"; case '\b': - res.append("\\b"); - break; + return "\\b"; case '\f': - res.append("\\f"); - break; + return "\\f"; case '\'': - res.append('\''); - break; + return "'"; case '"': - res.append("\\\""); - break; + return "\\\""; case '\\': - res.append("\\\\"); - break; + return "\\\\"; default: - if (c < 32 || c >= 127 && escapeUnicode) { - res.append("\\u").append(String.format("%04x", c)); - } else { - res.append((char) c); - } - break; + return null; } } diff --git a/jadx-core/src/test/java/jadx/tests/functional/StringUtilsTest.java b/jadx-core/src/test/java/jadx/tests/functional/StringUtilsTest.java index 72405fb8d..d97cb3909 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/StringUtilsTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/StringUtilsTest.java @@ -43,8 +43,9 @@ class StringUtilsTest { checkCharUnescape('a', "a"); checkCharUnescape(' ', " "); checkCharUnescape('\n', "\\n"); - checkCharUnescape('\'', "\\\'"); - checkCharUnescape('\0', "\\u0000"); + checkCharUnescape('\'', "\\'"); + + assertThat(stringUtils.unescapeChar('\0'), is("0")); } private void checkCharUnescape(char input, String result) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke4.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke4.java new file mode 100644 index 000000000..c21f3e4d7 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke4.java @@ -0,0 +1,24 @@ +package jadx.tests.integration.invoke; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestCastInOverloadedInvoke4 extends IntegrationTest { + + public static class TestCls { + public String test(String str) { + return str.replace('\n', ' '); + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return str.replace('\\n', ' ');"); + } +}