mirror of
https://github.com/skylot/jadx.git
synced 2025-02-15 10:39:56 +00:00
feat(gradle): handle non-final res ids for AGP >8.0.0 (PR #2362)
This commit is contained in:
parent
47f2e516e5
commit
7eab3c8534
@ -54,6 +54,7 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.fixaccessmodifiers.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.gradle.NonFinalResIdsVisitor;
|
||||
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
|
||||
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
|
||||
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
||||
@ -186,6 +187,7 @@ public class Jadx {
|
||||
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new FixSwitchOverEnum());
|
||||
passes.add(new NonFinalResIdsVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ClassModifier());
|
||||
|
@ -0,0 +1,118 @@
|
||||
package jadx.core.dex.visitors.gradle;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
|
||||
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.FixSwitchOverEnum;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||
import jadx.core.dex.visitors.regions.IRegionIterativeVisitor;
|
||||
import jadx.core.export.GradleInfoStorage;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "NonFinalResIdsVisitor",
|
||||
desc = "Detect usage of android resource constants in cases where constant expressions are required.",
|
||||
runAfter = FixSwitchOverEnum.class
|
||||
)
|
||||
public class NonFinalResIdsVisitor extends AbstractVisitor implements IRegionIterativeVisitor {
|
||||
|
||||
private boolean nonFinalResIdsFlagRequired = false;
|
||||
|
||||
private GradleInfoStorage gradleInfoStorage;
|
||||
|
||||
public void init(RootNode root) throws JadxException {
|
||||
gradleInfoStorage = root.getGradleInfoStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (nonFinalResIdsFlagRequired) {
|
||||
return false;
|
||||
}
|
||||
AnnotationsAttr annotationsList = cls.get(JadxAttrType.ANNOTATION_LIST);
|
||||
if (visitAnnotationList(annotationsList)) {
|
||||
return false;
|
||||
}
|
||||
return super.visit(cls);
|
||||
}
|
||||
|
||||
private static boolean isCustomResourceClass(ClassInfo cls) {
|
||||
ClassInfo parentClass = cls.getParentClass();
|
||||
return parentClass != null && parentClass.getShortName().equals("R") && !parentClass.getFullName().equals("android.R");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
AnnotationsAttr annotationsList = mth.get(JadxAttrType.ANNOTATION_LIST);
|
||||
if (visitAnnotationList(annotationsList)) {
|
||||
nonFinalResIdsFlagRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (nonFinalResIdsFlagRequired || !CodeFeaturesAttr.contains(mth, CodeFeaturesAttr.CodeFeature.SWITCH)) {
|
||||
return;
|
||||
}
|
||||
DepthRegionTraversal.traverseIterative(mth, this);
|
||||
}
|
||||
|
||||
private boolean visitAnnotationList(AnnotationsAttr annotationsList) {
|
||||
if (annotationsList != null) {
|
||||
for (IAnnotation annotation : annotationsList.getAll()) {
|
||||
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
|
||||
Object value = entry.getValue().getValue();
|
||||
if (value instanceof IFieldInfoRef && isCustomResourceClass(((IFieldInfoRef) value).getFieldInfo().getDeclClass())) {
|
||||
gradleInfoStorage.setNonFinalResIds(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||
if (nonFinalResIdsFlagRequired) {
|
||||
return false;
|
||||
}
|
||||
if (region instanceof SwitchRegion) {
|
||||
return detectSwitchOverResIds((SwitchRegion) region);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean detectSwitchOverResIds(SwitchRegion switchRegion) {
|
||||
for (SwitchRegion.CaseInfo caseInfo : switchRegion.getCases()) {
|
||||
for (Object key : caseInfo.getKeys()) {
|
||||
if (key instanceof FieldNode) {
|
||||
ClassNode topParentClass = ((FieldNode) key).getTopParentClass();
|
||||
if (AndroidResourcesUtils.isResourceClass(topParentClass) && !"android.R".equals(topParentClass.getFullName())) {
|
||||
this.nonFinalResIdsFlagRequired = true;
|
||||
gradleInfoStorage.setNonFinalResIds(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package jadx.core.export;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
@ -35,11 +37,26 @@ public class ExportGradleProject {
|
||||
saveProjectBuildGradle();
|
||||
saveApplicationBuildGradle();
|
||||
saveSettingsGradle();
|
||||
saveGradleProperties();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Gradle export failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveGradleProperties() throws IOException {
|
||||
GradleInfoStorage gradleInfo = root.getGradleInfoStorage();
|
||||
/*
|
||||
* For Android Gradle Plugin >=8.0.0 the property "android.nonFinalResIds=false" has to be set in
|
||||
* "gradle.properties" when resource identifiers are used as constant expressions.
|
||||
*/
|
||||
if (gradleInfo.isNonFinalResIds()) {
|
||||
File gradlePropertiesFile = new File(projectDir, "gradle.properties");
|
||||
try (FileOutputStream fos = new FileOutputStream(gradlePropertiesFile)) {
|
||||
fos.write("android.nonFinalResIds=false".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveProjectBuildGradle() throws IOException {
|
||||
TemplateFile tmpl = TemplateFile.fromResources("/export/build.gradle.tmpl");
|
||||
tmpl.save(new File(projectDir, "build.gradle"));
|
||||
|
@ -8,6 +8,8 @@ public class GradleInfoStorage {
|
||||
|
||||
private boolean useApacheHttpLegacy;
|
||||
|
||||
private boolean nonFinalResIds;
|
||||
|
||||
public boolean isVectorPathData() {
|
||||
return vectorPathData;
|
||||
}
|
||||
@ -31,4 +33,12 @@ public class GradleInfoStorage {
|
||||
public void setUseApacheHttpLegacy(boolean useApacheHttpLegacy) {
|
||||
this.useApacheHttpLegacy = useApacheHttpLegacy;
|
||||
}
|
||||
|
||||
public boolean isNonFinalResIds() {
|
||||
return nonFinalResIds;
|
||||
}
|
||||
|
||||
public void setNonFinalResIds(boolean nonFinalResIds) {
|
||||
this.nonFinalResIds = nonFinalResIds;
|
||||
}
|
||||
}
|
||||
|
@ -67,4 +67,12 @@ public abstract class ExportGradleTest {
|
||||
protected String getSettingsGradle() {
|
||||
return loadFileContent(new File(exportDir, "settings.gradle"));
|
||||
}
|
||||
|
||||
protected File getGradleProperiesFile() {
|
||||
return new File(exportDir, "gradle.properties");
|
||||
}
|
||||
|
||||
protected String getGradleProperies() {
|
||||
return loadFileContent(getGradleProperiesFile());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package jadx.tests.export;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.core.export.GradleInfoStorage;
|
||||
import jadx.tests.api.ExportGradleTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestNonFinalResIds extends ExportGradleTest {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
GradleInfoStorage gradleInfo = getRootNode().getGradleInfoStorage();
|
||||
gradleInfo.setNonFinalResIds(false);
|
||||
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
|
||||
Assertions.assertFalse(getGradleProperiesFile().exists());
|
||||
|
||||
gradleInfo.setNonFinalResIds(true);
|
||||
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml");
|
||||
assertThat(getGradleProperies()).containsOne("android.nonFinalResIds=false");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user