fix: resolve rename of inner class using full name (#1997)

This commit is contained in:
Skylot
2025-10-14 22:20:36 +01:00
parent 654cf5e4fb
commit d7c6be5664
10 changed files with 144 additions and 19 deletions
@@ -4,7 +4,6 @@ import java.io.Closeable;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -245,8 +244,12 @@ public class JadxArgs implements Closeable {
return inputFiles;
}
public void addInputFile(File inputFile) {
this.inputFiles.add(inputFile);
}
public void setInputFile(File inputFile) {
this.inputFiles = Collections.singletonList(inputFile);
addInputFile(inputFile);
}
public void setInputFiles(List<File> inputFiles) {
@@ -23,9 +23,9 @@ public final class ClassInfo implements Comparable<ClassInfo> {
@Nullable
private ClassAliasInfo alias;
private ClassInfo(RootNode root, ArgType type) {
private ClassInfo(RootNode root, ArgType type, boolean canBeInner) {
this.type = type;
splitAndApplyNames(root, type, root.getArgs().isMoveInnerClasses());
splitAndApplyNames(root, type, canBeInner);
}
public static ClassInfo fromType(RootNode root, ArgType type) {
@@ -34,7 +34,8 @@ public final class ClassInfo implements Comparable<ClassInfo> {
if (cls != null) {
return cls;
}
ClassInfo newClsInfo = new ClassInfo(root, clsType);
boolean canBeInner = root.getArgs().isMoveInnerClasses();
ClassInfo newClsInfo = new ClassInfo(root, clsType, canBeInner);
return root.getInfoStorage().putCls(newClsInfo);
}
@@ -42,6 +43,10 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return fromType(root, ArgType.object(clsName));
}
public static ClassInfo fromNameWithoutCache(RootNode root, String fullClsName, boolean canBeInner) {
return new ClassInfo(root, ArgType.object(fullClsName), canBeInner);
}
private static ArgType checkClassType(ArgType type) {
if (type == null) {
throw new JadxRuntimeException("Null class type");
@@ -624,32 +624,36 @@ public class ClassNode extends NotificationAttrNode
*/
@Override
public void rename(String newName) {
int lastDot = newName.lastIndexOf('.');
if (lastDot == -1) {
if (newName.indexOf('.') == -1) {
clsInfo.changeShortName(newName);
return;
}
if (clsInfo.isInner()) {
addWarn("Can't change package for inner class: " + this + " to " + newName);
return;
}
// full name provided
ClassInfo newClsInfo = ClassInfo.fromNameWithoutCache(root, newName, clsInfo.isInner());
// change class package
String newPkg = newName.substring(0, lastDot);
String newShortName = newName.substring(lastDot + 1);
if (changeClassNodePackage(newPkg)) {
clsInfo.changePkgAndName(newPkg, newShortName);
} else {
String newPkg = newClsInfo.getPackage();
String newShortName = newClsInfo.getShortName();
if (clsInfo.isInner()) {
if (!newPkg.equals(clsInfo.getPackage())) {
addWarn("Can't change package for inner class: " + this + " to " + newName);
}
clsInfo.changeShortName(newShortName);
} else {
if (changeClassNodePackage(newPkg)) {
clsInfo.changePkgAndName(newPkg, newShortName);
} else {
clsInfo.changeShortName(newShortName);
}
}
}
private boolean changeClassNodePackage(String fullPkg) {
if (clsInfo.isInner()) {
throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo);
}
if (fullPkg.equals(clsInfo.getAliasPkg())) {
return false;
}
if (clsInfo.isInner()) {
throw new JadxRuntimeException("Can't change package for inner class: " + clsInfo);
}
root.removeClsFromPackage(packageNode, this);
packageNode = PackageNode.getForClass(root, fullPkg, this);
root.sortPackages();
@@ -9,4 +9,7 @@ dependencies {
exclude("org.ow2.asm:asm")
exclude("net.fabricmc:tiny-remapper")
}
testRuntimeOnly(project(":jadx-plugins:jadx-dex-input"))
testRuntimeOnly(project(":jadx-plugins:jadx-smali-input"))
}
@@ -0,0 +1,58 @@
package jadx.plugins.mappings;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JavaClass;
import jadx.api.plugins.loader.JadxBasePluginLoader;
import jadx.core.plugins.files.SingleDirFilesGetter;
import static org.assertj.core.api.Assertions.assertThat;
public class BaseRenameMappingsTest {
private static final Logger LOG = LoggerFactory.getLogger(BaseRenameMappingsTest.class);
@TempDir
Path testDir;
Path outputDir;
JadxArgs jadxArgs;
String testResDir = "";
@BeforeEach
public void setUp() {
outputDir = testDir.resolve("output");
jadxArgs = new JadxArgs();
jadxArgs.setOutDir(outputDir.toFile());
jadxArgs.setFilesGetter(new SingleDirFilesGetter(testDir));
jadxArgs.setPluginLoader(new JadxBasePluginLoader());
}
public File loadResourceFile(String fileName) {
String path = testResDir + File.separator + fileName;
try {
URL resource = getClass().getClassLoader().getResource(path);
assertThat(resource).isNotNull();
return new File(resource.getFile());
} catch (Exception e) {
throw new RuntimeException("Failed to load resource file: " + path, e);
}
}
public void printClassesCode(List<JavaClass> classes) {
LOG.debug("Printing code for {} classes:", classes.size());
for (JavaClass jCls : classes) {
LOG.debug("Class: {}\n{}\n---\n", jCls.getFullName(), jCls.getCode());
}
}
}
@@ -0,0 +1,33 @@
package jadx.plugins.mappings;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import static org.assertj.core.api.Assertions.assertThat;
class TestInnerClassRename extends BaseRenameMappingsTest {
@Test
public void test() {
testResDir = "inner-cls-rename";
jadxArgs.getInputFiles().add(loadResourceFile("base.smali"));
jadxArgs.getInputFiles().add(loadResourceFile("inner.smali"));
jadxArgs.setUserRenamesMappingsPath(loadResourceFile("enigma.mapping").toPath());
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
List<JavaClass> classes = jadx.getClasses();
printClassesCode(classes);
assertThat(classes).hasSize(1);
JavaClass baseCls = classes.get(0);
assertThat(baseCls.getName()).isEqualTo("BaseCls");
List<JavaClass> innerClasses = baseCls.getInnerClasses();
assertThat(innerClasses).hasSize(1);
assertThat(innerClasses.get(0).getName()).isEqualTo("RenamedInner");
assertThat(baseCls.getCode()).contains("class RenamedInner {");
}
}
}
@@ -0,0 +1,2 @@
.class Ljadx/test/BaseCls;
.super Ljava/lang/Object;
@@ -0,0 +1,2 @@
CLASS jadx/test/BaseCls
CLASS Inner RenamedInner
@@ -0,0 +1,2 @@
.class Ljadx/test/BaseCls$Inner;
.super Ljava/lang/Object;
@@ -0,0 +1,13 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>