diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 6c725145a..5f7b173d8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -49,6 +49,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN private final RootNode root; private final int clsDefOffset; + @Nullable private final Path inputPath; private final ClassInfo clsInfo; @@ -132,8 +133,16 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); } - // empty synthetic class - public ClassNode(RootNode root, String name, int accessFlags) { + public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) { + ClassNode cls = new ClassNode(root, name, accessFlags); + cls.add(AFlag.SYNTHETIC); + cls.setState(ProcessState.PROCESS_COMPLETE); + root.addClassNode(cls); + return cls; + } + + // Create empty class + private ClassNode(RootNode root, String name, int accessFlags) { this.root = root; this.inputPath = null; this.clsDefOffset = 0; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 84a63ef00..93ac20714 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -106,9 +106,8 @@ public class RootNode { if (name == null || name.isEmpty()) { name = "CLASS_" + typeStr; } - ClassNode clsNode = new ClassNode(this, name, classData.getAccessFlags()); + ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags()); ErrorsCounter.error(clsNode, "Load error", exc); - addClassNode(clsNode); } public void addClassNode(ClassNode clsNode) { diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index d59accfb8..393ddbe3e 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.TreeMap; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +39,6 @@ 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"; @@ -59,14 +57,10 @@ public class AndroidResourcesUtils { if (!candidates.isEmpty()) { LOG.info("Found several 'R' class candidates: {}", candidates); } - LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName); - resCls = makeClass(root, fullName, resStorage); - if (resCls == null) { - // We are in an APK without code therefore we don't have to update an 'R' class with the resources - return null; - } - addResourceFields(resCls, resStorage, false); - return resCls; + LOG.info("App 'R' class not found, put all resources ids into : '{}'", fullName); + ClassNode newResCls = makeClass(root, fullName, resStorage); + addResourceFields(newResCls, resStorage, false); + return newResCls; } public static boolean handleAppResField(CodeWriter code, ClassGen clsGen, ClassInfo declClass) { @@ -80,9 +74,8 @@ public class AndroidResourcesUtils { return false; } - @Nullable private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) { - ClassNode rCls = new ClassNode(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); + ClassNode rCls = ClassNode.addSyntheticClass(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); rCls.addAttr(AType.COMMENTS, "This class is generated by JADX"); rCls.setState(ProcessState.PROCESS_COMPLETE); return rCls; @@ -131,11 +124,11 @@ public class AndroidResourcesUtils { @NotNull private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { - ClassNode newTypeCls = new ClassNode(resCls.root(), resCls.getFullName() + '$' + typeName, + ClassNode newTypeCls = ClassNode.addSyntheticClass(resCls.root(), resCls.getFullName() + '$' + typeName, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); resCls.addInnerClass(newTypeCls); if (rClsExists) { - newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); + newTypeCls.addAttr(AType.COMMENTS, "Added by JADX"); } return newTypeCls; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java similarity index 94% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java index 7f01c2965..a7bb2f302 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import org.junit.jupiter.api.Test; diff --git a/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java new file mode 100644 index 000000000..cc097bee2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java @@ -0,0 +1,63 @@ +package jadx.tests.integration.android; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; + +public class TestRFieldRestore extends IntegrationTest { + + public static class TestCls { + public int test() { + return 2131230730; + } + } + + @Test + public void test() { + // unknown R class + disableCompilation(); + + Map map = new HashMap<>(); + int buttonConstValue = 2131230730; + map.put(buttonConstValue, "id.Button"); + setResMap(map); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + assertThat(code, containsOne("return R.id.Button;")); + assertThat(code, not(containsString("import R;"))); + + // check 'R' class + ClassNode rCls = cls.root().searchClassByFullAlias("R"); + assertThat(rCls, notNullValue()); + + // check inner 'id' class + List innerClasses = rCls.getInnerClasses(); + assertThat(innerClasses, hasSize(1)); + ClassNode idCls = innerClasses.get(0); + assertThat(idCls.getShortName(), is("id")); + + // check 'Button' field + FieldNode buttonField = idCls.searchFieldByName("Button"); + assertThat(buttonField, notNullValue()); + FieldInitAttr fieldInitAttr = buttonField.get(AType.FIELD_INIT); + Integer buttonValue = (Integer) fieldInitAttr.getEncodedValue().getValue(); + assertThat(buttonValue, is(buttonConstValue)); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java similarity index 94% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java index 4ea2d019c..a672b4ce9 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import java.util.HashMap; import java.util.Map; diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java similarity index 97% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java index 7fbca415b..1d9a5049e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java deleted file mode 100644 index 785bb7875..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java +++ /dev/null @@ -1,38 +0,0 @@ -package jadx.tests.integration.inner; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import jadx.core.dex.nodes.ClassNode; -import jadx.tests.api.IntegrationTest; - -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; - -public class TestRFieldRestore extends IntegrationTest { - - public static class TestCls { - public int test() { - return 2131230730; - } - } - - @Test - public void test() { - // unknown R class - disableCompilation(); - - Map 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, not(containsString("import R;"))); - } -}