From 9856b6d3c5426ce37a742ee06f5c9f968a032217 Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 21 Feb 2019 19:14:55 +0300 Subject: [PATCH] fix: remove invalid chars from class names (#453) --- .../java/jadx/core/deobf/Deobfuscator.java | 16 +----- .../main/java/jadx/core/deobf/NameMapper.java | 51 +++++++++++++++++++ .../jadx/core/dex/visitors/RenameVisitor.java | 22 +++++--- .../java/jadx/core/deobf/NameMapperTest.java | 38 ++++++++++++++ 4 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 859341f95..e555b05de 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -483,21 +483,7 @@ public class Deobfuscator { if (name.length() > maxLength) { return "x" + Integer.toHexString(name.hashCode()); } - if (!NameMapper.isAllCharsPrintable(name)) { - return removeInvalidChars(name); - } - return name; - } - - private String removeInvalidChars(String name) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { - int ch = name.charAt(i); - if (NameMapper.isPrintableChar(ch)) { - sb.append((char) ch); - } - } - return sb.toString(); + return NameMapper.removeInvalidCharsMiddle(name); } private void dumpClassAlias(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java index 4e8e75686..9262f051f 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java +++ b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java @@ -91,6 +91,14 @@ public class NameMapper { && isAllCharsPrintable(str); } + public static boolean isValidIdentifierStart(int codePoint) { + return Character.isJavaIdentifierStart(codePoint); + } + + public static boolean isValidIdentifierPart(int codePoint) { + return Character.isJavaIdentifierPart(codePoint); + } + public static boolean isPrintableChar(int c) { return 32 <= c && c <= 126; } @@ -105,6 +113,49 @@ public class NameMapper { return true; } + /** + * Return modified string with removed: + *

+ * Note: this 'middle' method must be used with prefixed string: + *

+ */ + public static String removeInvalidCharsMiddle(String name) { + if (isValidIdentifier(name) && isAllCharsPrintable(name)) { + return name; + } + int len = name.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + int codePoint = name.codePointAt(i); + if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) { + sb.append((char) codePoint); + } + } + return sb.toString(); + } + + /** + * Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle} + *

+ * Prepend prefix if first char is not valid as java identifier start char. + */ + public static String removeInvalidChars(String name, String prefix) { + String result = removeInvalidCharsMiddle(name); + if (!result.isEmpty()) { + int codePoint = result.codePointAt(0); + if (!isValidIdentifierStart(codePoint)) { + return prefix + result; + } + } + return result; + } + private NameMapper() { } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 802e27942..c3cf80bbe 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -72,14 +72,9 @@ public class RenameVisitor extends AbstractVisitor { ClassInfo classInfo = cls.getClassInfo(); ClassInfo alias = classInfo.getAlias(); String clsName = alias.getShortName(); - String newShortName = null; - char firstChar = clsName.charAt(0); - if (Character.isDigit(firstChar)) { - newShortName = Consts.ANONYMOUS_CLASS_PREFIX + clsName; - } else if (firstChar == '$') { - newShortName = "C" + clsName; - } - if (newShortName != null) { + + String newShortName = fixClsShortName(clsName); + if (!newShortName.equals(clsName)) { classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true)); } if (alias.getPackage().isEmpty()) { @@ -89,6 +84,17 @@ public class RenameVisitor extends AbstractVisitor { } } + private String fixClsShortName(String clsName) { + char firstChar = clsName.charAt(0); + if (Character.isDigit(firstChar)) { + return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName); + } + if (firstChar == '$') { + return 'C' + NameMapper.removeInvalidCharsMiddle(clsName); + } + return NameMapper.removeInvalidChars(clsName, "C"); + } + private void checkFields(ClassNode cls) { Set names = new HashSet<>(); for (FieldNode field : cls.getFields()) { diff --git a/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java b/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java new file mode 100644 index 000000000..b5ff7b203 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java @@ -0,0 +1,38 @@ +package jadx.core.deobf; + +import org.junit.Test; + +import static jadx.core.deobf.NameMapper.isValidIdentifier; +import static jadx.core.deobf.NameMapper.removeInvalidChars; +import static jadx.core.deobf.NameMapper.removeInvalidCharsMiddle; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class NameMapperTest { + + @Test + public void validIdentifiers() { + assertThat(isValidIdentifier("ACls"), is(true)); + } + + @Test + public void notValidIdentifiers() { + assertThat(isValidIdentifier("1cls"), is(false)); + assertThat(isValidIdentifier("-cls"), is(false)); + assertThat(isValidIdentifier("A-cls"), is(false)); + } + + @Test + public void testRemoveInvalidCharsMiddle() { + assertThat(removeInvalidCharsMiddle("1cls"), is("1cls")); + assertThat(removeInvalidCharsMiddle("-cls"), is("cls")); + assertThat(removeInvalidCharsMiddle("A-cls"), is("Acls")); + } + + @Test + public void testRemoveInvalidChars() { + assertThat(removeInvalidChars("1cls", "C"), is("C1cls")); + assertThat(removeInvalidChars("-cls", "C"), is("cls")); + assertThat(removeInvalidChars("A-cls", "C"), is("Acls")); + } +}