Commit f6f883b9 authored by Skylot's avatar Skylot

fix: change resource fields generations in R class (#308)

parent 5de4d079
......@@ -34,6 +34,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodegenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
......@@ -105,9 +106,8 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
CodegenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
......@@ -296,6 +296,7 @@ public class ClassGen {
}
code.add(';');
} else {
CodegenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
......@@ -325,9 +326,9 @@ public class ClassGen {
}
}
private void insertDecompilationProblems(CodeWriter code, MethodNode mth) {
List<JadxError> errors = mth.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = mth.getAll(AType.JADX_WARN);
private void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = node.getAll(AType.JADX_WARN);
if (!errors.isEmpty()) {
errors.forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
......@@ -351,6 +352,7 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
CodegenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
......
......@@ -36,6 +36,7 @@ public class AType<T extends IAttribute> {
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
public static final AType<AttrList<String>> COMMENTS = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
......
......@@ -15,8 +15,8 @@ import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ResRefField;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.ErrorsCounter;
public class ConstStorage {
......@@ -101,15 +101,15 @@ public class ConstStorage {
@Nullable
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
if (!replaceEnabled) {
return null;
}
DexNode dex = cls.dex();
if (value instanceof Integer) {
String str = resourcesNames.get(value);
if (str != null) {
return new ResRefField(dex, str.replace('/', '.'));
}
FieldNode rField = getResourceField((Integer) value, dex);
if (rField != null) {
return rField;
}
if (!replaceEnabled) {
return null;
}
boolean foundInGlobal = globalValues.contains(value);
if (foundInGlobal && !searchGlobal) {
......@@ -140,6 +140,31 @@ public class ConstStorage {
}
@Nullable
private FieldNode getResourceField(Integer value, DexNode dex) {
String str = resourcesNames.get(value);
if (str == null) {
return null;
}
ClassNode appResClass = dex.root().getAppResClass();
if (appResClass == null) {
return null;
}
String[] parts = str.split("/", 2);
if (parts.length != 2) {
return null;
}
String typeName = parts[0];
String fieldName = parts[1];
for (ClassNode innerClass : appResClass.getInnerClasses()) {
if (innerClass.getShortName().equals(typeName)) {
return innerClass.searchFieldByName(fieldName);
}
}
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
return null;
}
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType();
if (type == null) {
......
......@@ -13,7 +13,6 @@ import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import com.android.dx.rop.code.AccessFlags;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -51,7 +50,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
private final List<MethodNode> methods;
private final List<FieldNode> fields;
private List<ClassNode> innerClasses = Collections.emptyList();
private List<ClassNode> innerClasses = new ArrayList<>();
// store decompiled code
private CodeWriter code;
......@@ -132,14 +131,16 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
}
// empty synthetic class
public ClassNode(DexNode dex, ClassInfo clsInfo) {
public ClassNode(DexNode dex, String name, int accessFlags) {
this.dex = dex;
this.clsInfo = clsInfo;
this.interfaces = Collections.emptyList();
this.methods = Collections.emptyList();
this.fields = Collections.emptyList();
this.accessFlags = new AccessInfo(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_SYNTHETIC, AFType.CLASS);
this.clsInfo = ClassInfo.fromName(dex.root(), name);
this.interfaces = new ArrayList<>();
this.methods = new ArrayList<>();
this.fields = new ArrayList<>();
this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS);
this.parentClass = this;
dex.addClassNode(this);
}
private void loadAnnotations(ClassDef cls) {
......@@ -368,10 +369,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<>(3);
}
innerClasses.add(cls);
cls.parentClass = this;
}
public boolean isEnum() {
......
......@@ -46,11 +46,14 @@ public class DexNode implements IDexNode {
public void loadClasses() {
for (ClassDef cls : dexBuf.classDefs()) {
ClassNode clsNode = new ClassNode(this, cls);
addClassNode(new ClassNode(this, cls));
}
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
}
void initInnerClasses() {
// move inner classes
......
package jadx.core.dex.nodes;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
public class ResRefField extends FieldNode {
public ResRefField(DexNode dex, String str) {
super(dex.root().getAppResClass(),
FieldInfo.from(dex, dex.root().getAppResClass().getClassInfo(), str, ArgType.INT),
AccessFlags.ACC_PUBLIC);
}
}
......@@ -37,11 +37,12 @@ public class RootNode {
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
private ClspGraph clsp;
private List<DexNode> dexNodes;
@Nullable
private String appPackage;
@Nullable
private ClassNode appResClass;
private ClspGraph clsp;
public RootNode(JadxArgs args) {
this.args = args;
......@@ -90,8 +91,10 @@ public class RootNode {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
processResources(parser.getResStorage());
}
ResourceStorage resStorage = parser.getResStorage();
public void processResources(ResourceStorage resStorage) {
constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage();
appResClass = AndroidResourcesUtils.searchAppResClass(this, resStorage);
......
......@@ -52,7 +52,7 @@ public class EnumVisitor extends AbstractVisitor {
}
}
if (staticMethod == null) {
ErrorsCounter.classError(cls, "Enum class init method not found");
ErrorsCounter.classWarn(cls, "Enum class init method not found");
return true;
}
......
package jadx.core.utils;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
public class CodegenUtils {
public static void addComments(CodeWriter code, AttrNode node) {
for (String comment : node.getAll(AType.COMMENTS)) {
code.startLine("/* ").add(comment).add(" */");
}
}
}
......@@ -74,8 +74,8 @@ public class ErrorsCounter {
return cls.dex().root().getErrorsCounter().addError(cls, errorMsg, e);
}
public static String classError(ClassNode cls, String errorMsg) {
return classError(cls, errorMsg, null);
public static String classWarn(ClassNode cls, String warnMsg) {
return cls.dex().root().getErrorsCounter().addWarning(cls, warnMsg);
}
public static String methodError(MethodNode mth, String errorMsg, Throwable e) {
......
package jadx.core.utils.android;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.android.dx.rop.code.AccessFlags;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ProcessState;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
......@@ -28,22 +33,29 @@ public class AndroidResourcesUtils {
private AndroidResourcesUtils() {
}
@Nullable
public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) {
String appPackage = root.getAppPackage();
String fullName = appPackage != null ? appPackage + ".R" : "R";
ClassNode resCls = root.searchClassByName(fullName);
if (resCls != null) {
addResourceFields(resCls, resStorage, true);
return resCls;
}
LOG.info("Can't find 'R' class in app package: {}", appPackage);
List<ClassNode> candidates = root.searchClassByShortName("R");
if (candidates.size() == 1) {
return candidates.get(0);
resCls = candidates.get(0);
addResourceFields(resCls, resStorage, true);
return resCls;
}
if (!candidates.isEmpty()) {
LOG.info("Found several 'R' class candidates: {}", candidates);
}
LOG.warn("Unknown 'R' class, create references to '{}'", fullName);
return makeClass(root, fullName, resStorage);
LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName);
resCls = makeClass(root, fullName, resStorage);
addResourceFields(resCls, resStorage, false);
return resCls;
}
public static boolean handleAppResField(CodeWriter code, ClassGen clsGen, ClassInfo declClass) {
......@@ -57,48 +69,45 @@ public class AndroidResourcesUtils {
return false;
}
@Nullable
private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) {
List<DexNode> dexNodes = root.getDexNodes();
if (dexNodes.isEmpty()) {
return null;
}
ClassInfo r = ClassInfo.fromName(root, clsName);
ClassNode classNode = new ClassNode(dexNodes.get(0), r);
generateMissingRCode(classNode, resStorage);
return classNode;
ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL);
rCls.addAttr(AType.COMMENTS, "This class is generated by JADX");
rCls.setState(ProcessState.PROCESSED);
return rCls;
}
private static void generateMissingRCode(ClassNode cls, ResourceStorage resStorage) {
Map<String, List<ResourceEntry>> sortedMap = new HashMap<>();
for(ResourceEntry ri : resStorage.getResources()) {
List<ResourceEntry> entries = sortedMap.get(ri.getTypeName());
if(entries == null) {
entries = new LinkedList<>();
sortedMap.put(ri.getTypeName(), entries);
private static void addResourceFields(ClassNode cls, ResourceStorage resStorage, boolean rClsExists) {
Map<String, ClassNode> innerClsMap = new TreeMap<>();
if (rClsExists) {
for (ClassNode innerClass : cls.getInnerClasses()) {
innerClsMap.put(innerClass.getShortName(), innerClass);
}
}
for (ResourceEntry resource : resStorage.getResources()) {
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
ClassNode newTypeCls = new ClassNode(cls.dex(), cls.getFullName() + "$" + name,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
cls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
}
return newTypeCls;
});
FieldNode rField = typeCls.searchFieldByName(resource.getKeyName());
if (rField == null) {
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resource.getKeyName(), ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
rField.addAttr(FieldInitAttr.constValue(resource.getId()));
typeCls.getFields().add(rField);
if (rClsExists) {
rField.addAttr(AType.COMMENTS, "added by JADX");
}
entries.add(ri);
}
Set<String> addedValues = new HashSet<>();
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';').newLine();
}
clsCode.startLine("public final class ").add(cls.getShortName()).add(" {").incIndent();
for(String typeName : sortedMap.keySet()) {
clsCode.startLine("public static final class ").add(typeName).add(" {").incIndent();
for(ResourceEntry ri : sortedMap.get(typeName)) {
if(addedValues.add(ri.getTypeName() + "." + ri.getKeyName())) {
clsCode.startLine("public static final int ").add(ri.getKeyName()).add(" = ")
.add("" + ri.getId()).add(";");
}
}
clsCode.decIndent();
clsCode.add("}");
}
clsCode.decIndent();
clsCode.add("}");
cls.setCode(clsCode);
}
}
......@@ -27,7 +27,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.tests.api.compiler.DynamicCompiler;
import jadx.tests.api.compiler.StaticCompiler;
import jadx.tests.api.utils.TestUtils;
......@@ -99,7 +100,7 @@ public abstract class IntegrationTest extends TestUtils {
fail(e.getMessage());
}
RootNode root = JadxInternalAccess.getRoot(d);
root.getConstValues().getResourcesNames().putAll(resMap);
insertResources(root);
ClassNode cls = root.searchClassByName(clsName);
assertThat("Class not found: " + clsName, cls, notNullValue());
......@@ -121,6 +122,20 @@ public abstract class IntegrationTest extends TestUtils {
return cls;
}
private void insertResources(RootNode root) {
if (resMap.isEmpty()) {
return;
}
ResourceStorage resStorage = new ResourceStorage();
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);
}
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
List<IDexTreeVisitor> passes = Jadx.getPassesList(jadx.getArgs());
ProcessClass.process(cls, passes, new CodeGen());
......
......@@ -25,15 +25,12 @@ public class TestRFieldRestore2 extends IntegrationTest {
@Test
public void test() {
// unknown id.Button
disableCompilation();
Map<Integer, String> map = new HashMap<>();
map.put(2131230730, "id.Button");
setResMap(map);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("return R.id.Button;"));
assertThat(code, containsOne("R.id.Button;"));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment