feat: improve better name algorithm (PR #2299)

This commit is contained in:
pubiqq
2024-10-10 22:01:57 +03:00
committed by GitHub
parent 964bd62d35
commit 3814951408
7 changed files with 219 additions and 4 deletions
@@ -81,7 +81,7 @@ public class SourceFileRename extends AbstractVisitor {
case ALWAYS:
return sourceName;
case IF_BETTER:
return BetterName.compareAndGet(sourceName, currentName);
return BetterName.getBetterClassName(sourceName, currentName);
case NEVER:
return currentName;
default:
@@ -15,6 +15,78 @@ public class BetterName {
private static final boolean DEBUG = false;
private static final double TOLERANCE = 0.001;
/**
* Compares two class names and returns the "better" one.
* If both names are equally good, {@code firstName} is returned.
*/
public static String getBetterClassName(String firstName, String secondName) {
return getBetterName(firstName, secondName);
}
/**
* Compares two resource names and returns the "better" one.
* If both names are equally good, {@code firstName} is returned.
*/
public static String getBetterResourceName(String firstName, String secondName) {
return getBetterName(firstName, secondName);
}
private static String getBetterName(String firstName, String secondName) {
if (Objects.equals(firstName, secondName)) {
return firstName;
}
if (StringUtils.isEmpty(firstName) || StringUtils.isEmpty(secondName)) {
return StringUtils.notEmpty(firstName)
? firstName
: secondName;
}
final var firstResult = analyze(firstName);
final var secondResult = analyze(secondName);
if (firstResult.digitCount != 0 || secondResult.digitCount != 0) {
final var firstRatio = (float) firstResult.digitCount / firstResult.length;
final var secondRatio = (float) secondResult.digitCount / secondResult.length;
if (Math.abs(secondRatio - firstRatio) >= TOLERANCE) {
return firstRatio <= secondRatio
? firstName
: secondName;
}
}
return firstResult.length >= secondResult.length
? firstName
: secondName;
}
private static AnalyzeResult analyze(String name) {
final var result = new AnalyzeResult();
StringUtils.visitCodePoints(name, cp -> {
if (Character.isDigit(cp)) {
result.digitCount++;
}
result.length++;
});
return result;
}
private static class AnalyzeResult {
private int length;
private int digitCount;
}
/**
* @deprecated Use {@link #getBetterClassName(String, String)} or
* {@link #getBetterResourceName(String, String)} instead.
*/
@Deprecated
public static String compareAndGet(String first, String second) {
if (Objects.equals(first, second)) {
return first;
@@ -32,6 +104,11 @@ public class BetterName {
return firstBetter ? first : second;
}
/**
* @deprecated This function is an implementation detail of deprecated
* {@link #compareAndGet(String, String)} and should not be used outside tests.
*/
@Deprecated
public static int calcRating(String str) {
int rating = str.length() * 3;
rating += differentCharsCount(str) * 20;
@@ -49,6 +126,7 @@ public class BetterName {
return rating;
}
@Deprecated
private static int differentCharsCount(String str) {
String lower = str.toLowerCase(Locale.ROOT);
Set<Integer> chars = new HashSet<>();
@@ -490,7 +490,7 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
public static String getBetterName(ResourceNameSource nameSource, String resName, String codeName) {
switch (nameSource) {
case AUTO:
return BetterName.compareAndGet(resName, codeName);
return BetterName.getBetterResourceName(resName, codeName);
case RESOURCES:
return resName;
case CODE:
@@ -7,12 +7,14 @@ import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestBetterName {
@Deprecated
@Test
public void test() {
expectFirst("color_main", "t0");
expectFirst("done", "oOo0oO0o");
}
@Deprecated
private void expectFirst(String first, String second) {
String best = BetterName.compareAndGet(first, second);
assertThat(best)
@@ -0,0 +1,67 @@
package jadx.core.utils;
import org.assertj.core.api.AbstractStringAssert;
import org.junit.jupiter.api.Test;
import static jadx.core.utils.BetterName.getBetterClassName;
import static org.assertj.core.api.Assertions.assertThat;
public class TestGetBetterClassName {
@Test
public void testGoodNamesVsGeneratedAliases() {
assertThatBetterClassName("AppCompatButton", "C2404e2").isEqualTo("AppCompatButton");
assertThatBetterClassName("ContextThemeWrapper", "C2106b1").isEqualTo("ContextThemeWrapper");
assertThatBetterClassName("ListPopupWindow", "C2344a3").isEqualTo("ListPopupWindow");
}
@Test
public void testShortGoodNamesVsGeneratedAliases() {
assertThatBetterClassName("Room", "C2937kh").isEqualTo("Room");
assertThatBetterClassName("Fade", "C1428qi").isEqualTo("Fade");
assertThatBetterClassName("Scene", "C4063yi").isEqualTo("Scene");
}
@Test
public void testGoodNamesVsGeneratedAliasesWithPrefix() {
assertThatBetterClassName("AppCompatActivity", "ActivityC2646h0").isEqualTo("AppCompatActivity");
assertThatBetterClassName("PagerAdapter", "AbstractC3038lk").isEqualTo("PagerAdapter");
assertThatBetterClassName("Lazy", "InterfaceC6434a").isEqualTo("Lazy");
assertThatBetterClassName("MembersInjector", "InterfaceC6435b").isEqualTo("MembersInjector");
assertThatBetterClassName("Subscriber", "InterfaceC6439c").isEqualTo("Subscriber");
}
@Test
public void testGoodNamesWithDigitsVsGeneratedAliases() {
assertThatBetterClassName("ISO8061Formatter", "C1121uq4").isEqualTo("ISO8061Formatter");
assertThatBetterClassName("Jdk9Platform", "C1189rn6").isEqualTo("Jdk9Platform");
assertThatBetterClassName("WrappedDrawableApi14", "C2847i9").isEqualTo("WrappedDrawableApi14");
assertThatBetterClassName("WrappedDrawableApi21", "C2888j9").isEqualTo("WrappedDrawableApi21");
}
@Test
public void testShortNamesVsLongNames() {
assertThatBetterClassName("az", "Observer").isEqualTo("Observer");
assertThatBetterClassName("bb", "RenderEvent").isEqualTo("RenderEvent");
assertThatBetterClassName("aaaa", "FontUtils").isEqualTo("FontUtils");
}
/**
* Tests {@link BetterName#getBetterClassName(String, String)} on equally good names.
* In this case, according to the documentation, the method should return the first argument.
*
* @see BetterName#getBetterClassName(String, String)
*/
@Test
public void testEquallyGoodNames() {
assertThatBetterClassName("AAAA", "BBBB").isEqualTo("AAAA");
assertThatBetterClassName("BBBB", "AAAA").isEqualTo("BBBB");
assertThatBetterClassName("XYXYXY", "YZYZYZ").isEqualTo("XYXYXY");
assertThatBetterClassName("YZYZYZ", "XYXYXY").isEqualTo("YZYZYZ");
}
private AbstractStringAssert<?> assertThatBetterClassName(String firstName, String secondName) {
return assertThat(getBetterClassName(firstName, secondName));
}
}
@@ -0,0 +1,37 @@
package jadx.core.utils;
import org.assertj.core.api.AbstractStringAssert;
import org.junit.jupiter.api.Test;
import static jadx.core.utils.BetterName.getBetterResourceName;
import static org.assertj.core.api.Assertions.assertThat;
public class TestGetBetterResourceName {
@Test
public void testGoodNamesVsSyntheticNames() {
assertThatBetterResourceName("color_main", "t0").isEqualTo("color_main");
assertThatBetterResourceName("done", "oOo0oO0o").isEqualTo("done");
}
/**
* Tests {@link BetterName#getBetterResourceName(String, String)} on equally good names.
* In this case, according to the documentation, the method should return the first argument.
*
* @see BetterName#getBetterResourceName(String, String)
*/
@Test
public void testEquallyGoodNames() {
assertThatBetterResourceName("AAAA", "BBBB").isEqualTo("AAAA");
assertThatBetterResourceName("BBBB", "AAAA").isEqualTo("BBBB");
assertThatBetterResourceName("Theme.AppCompat.Light", "Theme_AppCompat_Light")
.isEqualTo("Theme.AppCompat.Light");
assertThatBetterResourceName("Theme_AppCompat_Light", "Theme.AppCompat.Light")
.isEqualTo("Theme_AppCompat_Light");
}
private AbstractStringAssert<?> assertThatBetterResourceName(String firstName, String secondName) {
return assertThat(getBetterResourceName(firstName, secondName));
}
}
@@ -17,6 +17,14 @@ public class TestUsingSourceFileName extends SmaliTest {
.containsOne("class b {");
}
@Test
public void testIfBetterUseSourceName() {
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.IF_BETTER);
assertThat(searchCls(loadFromSmaliFiles(), "b"))
.code()
.containsOne("class a {");
}
@Test
public void testAlwaysUseSourceName() {
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.ALWAYS);
@@ -36,6 +44,17 @@ public class TestUsingSourceFileName extends SmaliTest {
.containsOne("/* compiled from: a.java */");
}
@Test
public void testIfBetterUseSourceNameWithDeobf() {
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.IF_BETTER);
enableDeobfuscation();
args.setDeobfuscationMinLength(100); // rename everything
assertThat(searchCls(loadFromSmaliFiles(), "b"))
.code()
.containsOne("class a {")
.containsOne("/* compiled from: a.java */");
}
@Test
public void testAlwaysUseSourceNameWithDeobf() {
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.ALWAYS);
@@ -66,9 +85,9 @@ public class TestUsingSourceFileName extends SmaliTest {
}
@Test
public void testDeprecatedUseSourceNameWithDeobf() {
public void testDeprecatedDontUseSourceNameWithDeobf() {
// noinspection deprecation
args.setUseSourceNameAsClassAlias(true);
args.setUseSourceNameAsClassAlias(false);
enableDeobfuscation();
args.setDeobfuscationMinLength(100); // rename everything
assertThat(searchCls(loadFromSmaliFiles(), "b"))
@@ -76,4 +95,16 @@ public class TestUsingSourceFileName extends SmaliTest {
.containsOne("class C0000b {")
.containsOne("/* compiled from: a.java */");
}
@Test
public void testDeprecatedUseSourceNameWithDeobf() {
// noinspection deprecation
args.setUseSourceNameAsClassAlias(true);
enableDeobfuscation();
args.setDeobfuscationMinLength(100); // rename everything
assertThat(searchCls(loadFromSmaliFiles(), "b"))
.code()
.containsOne("class a {")
.containsOne("/* compiled from: a.java */");
}
}