WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 7eab3c8

Browse files
authored
feat(gradle): handle non-final res ids for AGP >8.0.0 (PR #2362)
1 parent 47f2e51 commit 7eab3c8

File tree

6 files changed

+179
-0
lines changed

6 files changed

+179
-0
lines changed

jadx-core/src/main/java/jadx/core/Jadx.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
5555
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
5656
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
57+
import jadx.core.dex.visitors.gradle.NonFinalResIdsVisitor;
5758
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
5859
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
5960
import jadx.core.dex.visitors.prepare.CollectConstValues;
@@ -186,6 +187,7 @@ public static List<IDexTreeVisitor> getRegionsModePasses(JadxArgs args) {
186187

187188
passes.add(new EnumVisitor());
188189
passes.add(new FixSwitchOverEnum());
190+
passes.add(new NonFinalResIdsVisitor());
189191
passes.add(new ExtractFieldInit());
190192
passes.add(new FixAccessModifiers());
191193
passes.add(new ClassModifier());
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package jadx.core.dex.visitors.gradle;
2+
3+
import java.util.Map;
4+
5+
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
6+
import jadx.api.plugins.input.data.annotations.EncodedValue;
7+
import jadx.api.plugins.input.data.annotations.IAnnotation;
8+
import jadx.api.plugins.input.data.attributes.JadxAttrType;
9+
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
10+
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
11+
import jadx.core.dex.info.ClassInfo;
12+
import jadx.core.dex.nodes.ClassNode;
13+
import jadx.core.dex.nodes.FieldNode;
14+
import jadx.core.dex.nodes.IFieldInfoRef;
15+
import jadx.core.dex.nodes.IRegion;
16+
import jadx.core.dex.nodes.MethodNode;
17+
import jadx.core.dex.nodes.RootNode;
18+
import jadx.core.dex.regions.SwitchRegion;
19+
import jadx.core.dex.visitors.AbstractVisitor;
20+
import jadx.core.dex.visitors.FixSwitchOverEnum;
21+
import jadx.core.dex.visitors.JadxVisitor;
22+
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
23+
import jadx.core.dex.visitors.regions.IRegionIterativeVisitor;
24+
import jadx.core.export.GradleInfoStorage;
25+
import jadx.core.utils.android.AndroidResourcesUtils;
26+
import jadx.core.utils.exceptions.JadxException;
27+
28+
@JadxVisitor(
29+
name = "NonFinalResIdsVisitor",
30+
desc = "Detect usage of android resource constants in cases where constant expressions are required.",
31+
runAfter = FixSwitchOverEnum.class
32+
)
33+
public class NonFinalResIdsVisitor extends AbstractVisitor implements IRegionIterativeVisitor {
34+
35+
private boolean nonFinalResIdsFlagRequired = false;
36+
37+
private GradleInfoStorage gradleInfoStorage;
38+
39+
public void init(RootNode root) throws JadxException {
40+
gradleInfoStorage = root.getGradleInfoStorage();
41+
}
42+
43+
@Override
44+
public boolean visit(ClassNode cls) throws JadxException {
45+
if (nonFinalResIdsFlagRequired) {
46+
return false;
47+
}
48+
AnnotationsAttr annotationsList = cls.get(JadxAttrType.ANNOTATION_LIST);
49+
if (visitAnnotationList(annotationsList)) {
50+
return false;
51+
}
52+
return super.visit(cls);
53+
}
54+
55+
private static boolean isCustomResourceClass(ClassInfo cls) {
56+
ClassInfo parentClass = cls.getParentClass();
57+
return parentClass != null && parentClass.getShortName().equals("R") && !parentClass.getFullName().equals("android.R");
58+
}
59+
60+
@Override
61+
public void visit(MethodNode mth) throws JadxException {
62+
AnnotationsAttr annotationsList = mth.get(JadxAttrType.ANNOTATION_LIST);
63+
if (visitAnnotationList(annotationsList)) {
64+
nonFinalResIdsFlagRequired = true;
65+
return;
66+
}
67+
68+
if (nonFinalResIdsFlagRequired || !CodeFeaturesAttr.contains(mth, CodeFeaturesAttr.CodeFeature.SWITCH)) {
69+
return;
70+
}
71+
DepthRegionTraversal.traverseIterative(mth, this);
72+
}
73+
74+
private boolean visitAnnotationList(AnnotationsAttr annotationsList) {
75+
if (annotationsList != null) {
76+
for (IAnnotation annotation : annotationsList.getAll()) {
77+
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
78+
continue;
79+
}
80+
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
81+
Object value = entry.getValue().getValue();
82+
if (value instanceof IFieldInfoRef && isCustomResourceClass(((IFieldInfoRef) value).getFieldInfo().getDeclClass())) {
83+
gradleInfoStorage.setNonFinalResIds(true);
84+
return true;
85+
}
86+
}
87+
}
88+
}
89+
return false;
90+
}
91+
92+
@Override
93+
public boolean visitRegion(MethodNode mth, IRegion region) {
94+
if (nonFinalResIdsFlagRequired) {
95+
return false;
96+
}
97+
if (region instanceof SwitchRegion) {
98+
return detectSwitchOverResIds((SwitchRegion) region);
99+
}
100+
return false;
101+
}
102+
103+
private boolean detectSwitchOverResIds(SwitchRegion switchRegion) {
104+
for (SwitchRegion.CaseInfo caseInfo : switchRegion.getCases()) {
105+
for (Object key : caseInfo.getKeys()) {
106+
if (key instanceof FieldNode) {
107+
ClassNode topParentClass = ((FieldNode) key).getTopParentClass();
108+
if (AndroidResourcesUtils.isResourceClass(topParentClass) && !"android.R".equals(topParentClass.getFullName())) {
109+
this.nonFinalResIdsFlagRequired = true;
110+
gradleInfoStorage.setNonFinalResIds(true);
111+
return false;
112+
}
113+
}
114+
}
115+
}
116+
return false;
117+
}
118+
}

jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package jadx.core.export;
22

33
import java.io.File;
4+
import java.io.FileOutputStream;
45
import java.io.IOException;
6+
import java.nio.charset.StandardCharsets;
57
import java.util.ArrayList;
68
import java.util.EnumSet;
79
import java.util.List;
@@ -35,11 +37,26 @@ public void generateGradleFiles() {
3537
saveProjectBuildGradle();
3638
saveApplicationBuildGradle();
3739
saveSettingsGradle();
40+
saveGradleProperties();
3841
} catch (Exception e) {
3942
throw new JadxRuntimeException("Gradle export failed", e);
4043
}
4144
}
4245

46+
private void saveGradleProperties() throws IOException {
47+
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
48+
/*
49+
* For Android Gradle Plugin >=8.0.0 the property "android.nonFinalResIds=false" has to be set in
50+
* "gradle.properties" when resource identifiers are used as constant expressions.
51+
*/
52+
if (gradleInfo.isNonFinalResIds()) {
53+
File gradlePropertiesFile = new File(projectDir, "gradle.properties");
54+
try (FileOutputStream fos = new FileOutputStream(gradlePropertiesFile)) {
55+
fos.write("android.nonFinalResIds=false".getBytes(StandardCharsets.UTF_8));
56+
}
57+
}
58+
}
59+
4360
private void saveProjectBuildGradle() throws IOException {
4461
TemplateFile tmpl = TemplateFile.fromResources("/export/build.gradle.tmpl");
4562
tmpl.save(new File(projectDir, "build.gradle"));

jadx-core/src/main/java/jadx/core/export/GradleInfoStorage.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public class GradleInfoStorage {
88

99
private boolean useApacheHttpLegacy;
1010

11+
private boolean nonFinalResIds;
12+
1113
public boolean isVectorPathData() {
1214
return vectorPathData;
1315
}
@@ -31,4 +33,12 @@ public boolean isUseApacheHttpLegacy() {
3133
public void setUseApacheHttpLegacy(boolean useApacheHttpLegacy) {
3234
this.useApacheHttpLegacy = useApacheHttpLegacy;
3335
}
36+
37+
public boolean isNonFinalResIds() {
38+
return nonFinalResIds;
39+
}
40+
41+
public void setNonFinalResIds(boolean nonFinalResIds) {
42+
this.nonFinalResIds = nonFinalResIds;
43+
}
3444
}

jadx-core/src/test/java/jadx/tests/api/ExportGradleTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,12 @@ protected String getAppGradleBuild() {
6767
protected String getSettingsGradle() {
6868
return loadFileContent(new File(exportDir, "settings.gradle"));
6969
}
70+
71+
protected File getGradleProperiesFile() {
72+
return new File(exportDir, "gradle.properties");
73+
}
74+
75+
protected String getGradleProperies() {
76+
return loadFileContent(getGradleProperiesFile());
77+
}
7078
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package jadx.tests.export;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
6+
import jadx.core.export.GradleInfoStorage;
7+
import jadx.tests.api.ExportGradleTest;
8+
9+
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
10+
11+
public class TestNonFinalResIds extends ExportGradleTest {
12+
13+
@Test
14+
void test() {
15+
GradleInfoStorage gradleInfo = getRootNode().getGradleInfoStorage();
16+
gradleInfo.setNonFinalResIds(false);
17+
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
18+
Assertions.assertFalse(getGradleProperiesFile().exists());
19+
20+
gradleInfo.setNonFinalResIds(true);
21+
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
22+
assertThat(getGradleProperies()).containsOne("android.nonFinalResIds=false");
23+
}
24+
}

0 commit comments

Comments
 (0)