fix: process synthetic resource inner classes using common method (#2482)
This commit is contained in:
@@ -124,13 +124,15 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadPlugins();
|
||||
loadInputFiles();
|
||||
|
||||
root = new RootNode(args);
|
||||
root = new RootNode(this);
|
||||
root.init();
|
||||
root.setDecompilerRef(this);
|
||||
root.mergePasses(customPasses);
|
||||
// load classes and resources
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(resourcesLoader, getResources());
|
||||
root.finishClassLoad();
|
||||
root.initClassPath();
|
||||
// init passes
|
||||
root.mergePasses(customPasses);
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
loadFinished();
|
||||
|
||||
@@ -838,6 +838,9 @@ public class ClassNode extends NotificationAttrNode
|
||||
return clsInfo.getAliasShortName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Use {@link #getAlias()}
|
||||
*/
|
||||
@Deprecated
|
||||
public String getShortName() {
|
||||
return clsInfo.getAliasShortName();
|
||||
|
||||
@@ -100,7 +100,20 @@ public class RootNode {
|
||||
|
||||
private @Nullable ManifestAttributes manifestAttributes;
|
||||
|
||||
public RootNode(JadxDecompiler decompiler) {
|
||||
this(decompiler, decompiler.getArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Prefer {@link #RootNode(JadxDecompiler)}
|
||||
*/
|
||||
@Deprecated
|
||||
public RootNode(JadxArgs args) {
|
||||
this(null, args);
|
||||
}
|
||||
|
||||
private RootNode(@Nullable JadxDecompiler decompiler, JadxArgs args) {
|
||||
this.decompiler = decompiler;
|
||||
this.args = args;
|
||||
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
||||
this.processClasses = new ProcessClass(Jadx.getPassesList(args));
|
||||
@@ -131,6 +144,9 @@ public class RootNode {
|
||||
Utils.checkThreadInterrupt();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void finishClassLoad() {
|
||||
if (classes.size() != clsMap.size()) {
|
||||
// class name duplication detected
|
||||
markDuplicatedClasses(classes);
|
||||
@@ -715,10 +731,6 @@ public class RootNode {
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setDecompilerRef(JadxDecompiler jadxDecompiler) {
|
||||
this.decompiler = jadxDecompiler;
|
||||
}
|
||||
|
||||
public @Nullable JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class AndroidResourcesUtils {
|
||||
|
||||
public static boolean isResourceClass(ClassNode cls) {
|
||||
ClassNode parentClass = cls.getParentClass();
|
||||
return parentClass != null && parentClass.getShortName().equals("R");
|
||||
return parentClass != null && parentClass.getAlias().equals("R");
|
||||
}
|
||||
|
||||
private static final class ResClsInfo {
|
||||
@@ -109,7 +109,7 @@ public class AndroidResourcesUtils {
|
||||
for (ClassNode innerClass : resCls.getInnerClasses()) {
|
||||
ResClsInfo innerResCls = new ResClsInfo(innerClass);
|
||||
innerClass.getFields().forEach(field -> innerResCls.getFieldsMap().put(field.getName(), field));
|
||||
innerClsMap.put(innerClass.getShortName(), innerResCls);
|
||||
innerClsMap.put(innerClass.getAlias(), innerResCls);
|
||||
}
|
||||
}
|
||||
for (ResourceEntry resource : resStorage.getResources()) {
|
||||
@@ -142,24 +142,18 @@ public class AndroidResourcesUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ResClsInfo getClassForResType(ClassNode resCls, boolean rClsExists, String typeName) {
|
||||
String clsFullName = resCls.getFullName() + '$' + typeName;
|
||||
ClassInfo clsInfo = ClassInfo.fromName(resCls.root(), clsFullName);
|
||||
ClassNode existCls = resCls.root().resolveClass(clsInfo);
|
||||
RootNode root = resCls.root();
|
||||
String typeClsFullName = resCls.getClassInfo().makeRawFullName() + '$' + typeName;
|
||||
ClassInfo clsInfo = ClassInfo.fromName(root, typeClsFullName);
|
||||
ClassNode existCls = root.resolveClass(clsInfo);
|
||||
if (existCls != null) {
|
||||
if (!rClsExists && !existCls.isInner()) {
|
||||
// convert found res cls to inner for R class
|
||||
existCls.getClassInfo().convertToInner(resCls);
|
||||
resCls.addInnerClass(existCls);
|
||||
}
|
||||
ResClsInfo resClsInfo = new ResClsInfo(existCls);
|
||||
existCls.getFields().forEach(field -> resClsInfo.getFieldsMap().put(field.getName(), field));
|
||||
return resClsInfo;
|
||||
}
|
||||
ClassNode newTypeCls = ClassNode.addSyntheticClass(resCls.root(), clsInfo,
|
||||
ClassNode newTypeCls = ClassNode.addSyntheticClass(root, clsInfo,
|
||||
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
|
||||
resCls.addInnerClass(newTypeCls);
|
||||
if (rClsExists) {
|
||||
newTypeCls.addInfoComment("Added by JADX");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.tests.api;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -43,14 +44,19 @@ import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.CustomResourcesLoader;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -58,6 +64,10 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.xmlgen.BinaryXMLStrings;
|
||||
import jadx.core.xmlgen.IResTableParser;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResourceStorage;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.tests.api.compiler.CompilerOptions;
|
||||
@@ -258,16 +268,15 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
JadxDecompiler d = new JadxDecompiler(args);
|
||||
try {
|
||||
insertResources(d);
|
||||
d.load();
|
||||
return d;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Load failed", e);
|
||||
d.close();
|
||||
fail(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
RootNode root = JadxInternalAccess.getRoot(d);
|
||||
insertResources(root);
|
||||
return d;
|
||||
}
|
||||
|
||||
protected void decompileAndCheck(ClassNode cls) {
|
||||
@@ -346,18 +355,75 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private void insertResources(RootNode root) {
|
||||
/**
|
||||
* Insert mock resource table data ('.arsc' file)
|
||||
*/
|
||||
private void insertResources(JadxDecompiler decompiler) throws IOException {
|
||||
if (resMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ResourceStorage resStorage = new ResourceStorage(root.getArgs().getSecurity());
|
||||
String resTableName = "test-res-table";
|
||||
Path resTablePath = testDir.resolve(resTableName);
|
||||
File resTableFile = resTablePath.toFile().getAbsoluteFile();
|
||||
FileUtils.makeDirsForFile(resTableFile);
|
||||
Files.writeString(resTablePath, resTableName);
|
||||
JadxArgs jadxArgs = decompiler.getArgs();
|
||||
jadxArgs.getInputFiles().add(resTableFile);
|
||||
|
||||
// load mock file as 'arsc'
|
||||
decompiler.addCustomResourcesLoader(new CustomResourcesLoader() {
|
||||
@Override
|
||||
public boolean load(ResourcesLoader loader, List<ResourceFile> list, File file) {
|
||||
if (file == resTableFile) {
|
||||
list.add(ResourceFile.createResourceFile(decompiler, resTableFile, ResourceType.ARSC));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
});
|
||||
|
||||
// convert resources map to resource storage object
|
||||
ResourceStorage resStorage = new ResourceStorage(jadxArgs.getSecurity());
|
||||
for (Map.Entry<Integer, String> entry : resMap.entrySet()) {
|
||||
Integer id = entry.getKey();
|
||||
String name = entry.getValue();
|
||||
String[] parts = name.split("\\.");
|
||||
resStorage.add(new ResourceEntry(id, "", parts[0], parts[1], ""));
|
||||
}
|
||||
root.processResources(resStorage);
|
||||
|
||||
// mock res table parser to just return resource storage
|
||||
IResTableParser resTableParser = new IResTableParser() {
|
||||
@Override
|
||||
public void decode(InputStream inputStream) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceStorage getResStorage() {
|
||||
return resStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer decodeFiles() {
|
||||
return ResContainer.textResource(resTableName, new SimpleCodeInfo(resTableName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryXMLStrings getStrings() {
|
||||
return new BinaryXMLStrings();
|
||||
}
|
||||
};
|
||||
|
||||
// directly return generated resource storage instead parsing for mock res file
|
||||
decompiler.getResourcesLoader().addResTableParserProvider(resFile -> {
|
||||
if (resFile.getOriginalName().equals(resTableFile.getAbsolutePath())) {
|
||||
return resTableParser;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void runAutoCheck(ClassNode cls) {
|
||||
|
||||
Reference in New Issue
Block a user